I'm just starting with Dependency Injection using Dagger Framework, so it might be I'm missing something trivial, but I've spent more than 2 days just going over the code and the tutorial.
So, I am trying to use dagger dependency injection for my ViewModel
s, I found that I could do that with a feature called multi binding, which I've implemented. I have an AppComponent
and an ActivitySubComponent
. The AppComponent
installs a ViewModelFactoryModule
which @Binds
a ViewModelProvider.Factory
, I want this Module
in the Application because I think it should be independent of the Activity
life-cycle. And in my ActivitySubComponent
I have a ViewModelsModule
which @Binds
a ViewModel
.
Now per my understanding I have included ActivitySubComponent
in the ViewModelFactoryModule
which will add the subcomponent to the AppComponent
.
Edit :
Now when I make/build the project I get the following error. Apologies, the original error was:
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.mobile.presentation.viewmodels.build.ViewModelFactory(creators)
com.mobile.presentation.viewmodels.build.ViewModelFactory is provided at
com.mobile.app.AppComponent.obtainViewModelsFactory()
The following error was produced after adding following method to AppComponent.java
Map<Class<? extends ViewModel>, Provider<ViewModel>> obtainMap();
.
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is provided at
com.mobile.app.AppComponent.obtainMap()
It is also requested at:
com.mobile.presentation.viewmodels.build.ViewModelFactory(creators)
The following other entry points also depend on it:
com.mobile.app.AppComponent.obtainViewModelsFactory()
Enough jibber jabber, here is the relevant code:
AppComponent.java
@AppScope
@Component(
modules = {
AppModule.class,
ViewModelFactoryModule.class
})
public interface AppComponent {
App obtainApp();
ViewModelFactory obtainViewModelsFactory();
// I tried with adding the following line, out of sheer annoyance.
// Map<Class<? extends ViewModel>, Provider<ViewModel>> obtainMap();
}
ViewModelFactoryModule.java
@Module(subcomponents = ActivitySubComponent.class)
public abstract class ViewModelFactoryModule {
@AppScope
@Binds
public abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory);
}
ViewModelsFactory.java
@AppScope
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@NonNull
@Override
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);
}
}
}
ActivitySubComponent.java
// I have a separate module for my `LiveData`.
@ActivityScope
@Subcomponent(
modules = {
ActivityModule.class,
ViewModelsModule.class,
SplashLiveDataModule.class
}
)
public interface ActivitySubComponent {
Activity obtainActivity();
LiveData<InitialAPIResponse> obtainLiveInitialApiResponse();
LiveData<InitialSplashApiStatus> obtainLiveInitialSplashApiStatus();
@Subcomponent.Builder
interface Builder {
ActivitySubComponent build();
Builder setActivityModule(ActivityModule activityModule);
}
}
ViewModelsModule.java
@Module
public abstract class ViewModelsModule {
@ActivityScope
@Binds
@IntoMap
@ViewModelKey(SplashViewModel.class)
public abstract ViewModel provideVideoListViewModel(SplashViewModel viewModel);
}
Now, I understand from this that the AppComponent
cannot have direct access to ActivitySubComponent
, but it should be able to access the SubComponent
since a @Subcomponent
is generated as an inner class of the component (perhaps I'm missing something), and I don't think ViewModel
s should be bound to the application's component (I say this because if I move my ViewModelsModule
to the AppComponent
too I can run the code and everything works right), am I wrong in my logical approach?
dagger dependencies:
implementation 'com.google.dagger:dagger:2.22'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22'