Injecting ViewModels with an Activity context dependency
Injecting ViewModels with an Activity context dependency
I'm using dagger.android and want to inject ViewModels. On the other hand, I have an ApiModule
that depends on activity context,
ApiModule
Here is the AppComponent
AppComponent
@Singleton
@Component(modules =
AndroidSupportInjectionModule.class,
AppModule.class,
ActivityBindingModule.class,
ViewModelModule.class
)
public interface AppComponent extends AndroidInjector<MyApp>
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp>
The ViewModelModule
provides ViewModelProvider.Factory
ViewModelModule
@Module
abstract class ViewModelModule
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(MyAppViewModelFactory factory);
Here is the ActivityBindingModule
:
ActivityBindingModule
@Module
abstract class ActivityBindingModule
//MainActivity
@ActivityScoped
@ContributesAndroidInjector(modules =
MainActivityModule.class,
ApiModule.class
)
abstract MainActivity contributeMainActivityInjector();
@Module
abstract class MainActivityModule
@ActivityScoped
@Binds
abstract Activity bindMainActivity(MainActivity activity);
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel bindUserViewModel(UserViewModel userViewModel);
//SecondActivity
@ActivityScoped
@ContributesAndroidInjector(modules =
SecondActivityModule.class,
ApiModule.class
)
abstract SecondActivity contributeSecondActivityInjector();
@Module
abstract class SecondActivityModule
@ActivityScoped
@Binds
abstract Activity bindSecondActivity(SecondActivity activity);
@Binds
@IntoMap
@ViewModelKey(DetailViewModel.class)
abstract ViewModel bindDetailViewModel(DetailViewModel detailViewModel);
I have these injection hierarchies:
MainActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> UserViewModel --inject--> UserRepository --inject--> ApiService --inject--> Activity context
MainActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> UserViewModel --inject--> UserRepository --inject--> ApiService --inject--> Activity context
SecondActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> DetailViewModel --inject--> DetailRepository --inject--> ApiService --inject--> Activity context
SecondActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> DetailViewModel --inject--> DetailRepository --inject--> ApiService --inject--> Activity context
We provide:
Activity
MainActivityModule
SecondActivityModule
ApiService
ApiModule
ActivityBindingModule
UserRepository
DetailRepository
@Singleton
UserViewModel
MainActivityModule
DetailViewModel
SecondActivityModule
MyAppViewModelFactory
Here is MyAppViewModelFactory
:
MyAppViewModelFactory
@Singleton
public class MyAppViewModelFactoryimplements ViewModelProvider.Factory
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
MyAppViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators)
this.creators = creators;
@SuppressWarnings("unchecked")
@Override
@NonNull
public <T extends ViewModel> T create(@NonNull Class<T> modelClass)
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null)
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet())
if (modelClass.isAssignableFrom(entry.getKey()))
creator = entry.getValue();
break;
if (creator == null)
throw new IllegalArgumentException("unknown model class " + modelClass);
try
return (T) creator.get();
catch (Exception e)
throw new RuntimeException(e);
I got this error:
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
com.example.viewmodels.MyAppViewModelFactory.<init>(creators)
com.example.viewmodels.MyAppViewModelFactory is injected at
com.example.di.ViewModelModule.bindViewModelFactory(factory)
android.arch.lifecycle.ViewModelProvider.Factory is injected at
com.example.activities.MainActivity.viewModelFactory
com.example.activities.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.di.AppComponent ? com.example.di.ActivityBindingModule_ContributeMainActivityInjector.MainActivitySubcomponent
I tried to move bindUserViewModel
(from MainActivityModule
) and bindDetailViewModel
(from SecondActivityModule
) to ViewModelModule
and I got this error:
bindUserViewModel
MainActivityModule
bindDetailViewModel
SecondActivityModule
ViewModelModule
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] tk.medlynk.medlynk.http.ApiService cannot be provided without an @Provides-annotated method.
com.example.http.ApiService is injected at
com.example.repositories.UserRepository.<init>(apiService)
com.example.repositories.UserRepository is injected at
com.example.viewmodels.UserViewModel.<init>(userRepo)
com.example.viewmodels.UserViewModel is injected at
com.example.di.ViewModelModule.bindUserViewModel(userViewModel)
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
com.example.viewmodels.MyAppViewModelFactory.<init>(creators)
com.example.viewmodels.MyAppViewModelFactory is injected at
com.example.di.ViewModelModule.bindViewModelFactory(factory)
android.arch.lifecycle.ViewModelProvider.Factory is injected at
com.example.activities.MainActivity.viewModelFactory
com.example.activities.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.di.AppComponent ? com.example.di.ActivityBindingModule_ContributeMainActivityInjector.MainActivitySubcomponent
What am I missing here? the iosched project does the same thing without problem
2 Answers
2
Firstly, you need to restructure your Dagger2 setup in the Application and Activity levels.
Secondly, your ViewModelProviderFactory
class is somewhat complicated. I will restructure your code for you now.
ViewModelProviderFactory
Application Level
AppComponent
AppComponent
@Singleton
@Component(modules =
AndroidSupportInjectionModule.class,
AppModule.class,
ActivityBindingModule.class
)
public interface AppComponent extends AndroidInjector<MyApp>
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp>
ActivityBindingModule
ActivityBindingModule
@Module
abstract class ActivityBindingModule
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity contributeMainActivity();
//This goes for other activities
AppModule
This class provides only classes that are common and may be used in
any activity throughout the application.
AppModule
@Module
class AppModule
@Singleton
@Provides
//Your ApiService interface comes here
//Retrofit
//Database
App
class should extend DaggerApplication for simplicity. This automatically injects its members
App
class MyApp extends DaggerApplication
@Override
public void onCreate()
super.onCreate();
@Override
protected AndroidInjector<? extends DaggerApplication>
applicationInjector()
return DaggerHorizonMainComponent.builder().create(this);
Activity Level
MainActivityModule
This is where you have your ViewModel
and ViewModelProviderFactory
MainActivityModule
ViewModel
ViewModelProviderFactory
@Module
class MainACtivityModule
@Provides
MainActivityViewModel provideMainActivityViewModel()
return new MainActivityViewModel();
@Provides
ViewModelProvider.Factory provideViewModelProvider(MainActivityViewModel
viewModel)
return new ViewModelProviderFactory<>(viewModel);
//Your repository comes here too
ViewModel
class
ViewModel
class MyViewModel extends ViewModel
@Inject
public MyViewModel()
ViewModelProviderFactory
Just copy this code
ViewModelProviderFactory
public class ViewModelProviderFactory<V> implements
ViewModelProvider.Factory
private V viewModel;
public ViewModelProviderFactory(V viewModel)
this.viewModel = viewModel;
@Override
public <T extends ViewModel> T create(Class<T> modelClass)
if (modelClass.isAssignableFrom(viewModel.getClass()))
return (T) viewModel;
throw new IllegalArgumentException("Unknown class name");
Finally, in your MainActivity
class, you inject the ViewModelProviderFactory. You don't need to inject Dagger here since you are extending it to DaggerAppCompatActivity
MainActivity
public class Mainactivity extends DaggerAppCompatActivity
@Inject
ViewModelProvider.Factory factory;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
//You set your view here
viewmodel =
ViewModelProviders.of(this,factory).get(MyViewModel.class);
ViewModelProviderFactory
Let me know if there is any issue regarding the above codes or if you have any question. Hope this solves the issues you have
– princessdharmy
Aug 26 at 23:16
All your setting sounds correct. The main cause is ApiService
is not provided.
ApiService
error: [Dagger/MissingBinding]
[dagger.android.AndroidInjector.inject(T)]
tk.medlynk.medlynk.http.ApiService cannot be provided without an
@Provides-annotated method.
To resolve it you just provide it in some module.
class AppModule
@Singleton
@Provides
fun provideApiService(): ApiService
....
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Tnx, but I used
ViewModelProviderFactory
as same as google sample github.com/googlesamples/android-architecture-components/blob/… and same as github.com/google/iosched/blob/master/shared/src/main/java/com/…– Hafez Divandari
Aug 26 at 22:47