Dagger Android Tutorial 2
How to do dagger-android
In this article we show how to inject into ViewModels.
Note that we will not dive too deep into the inner workings of things, since they are extremely complex. If you want to get some level of insight into the topic read this article. But I try to give the clean solution and how you can put it in your head.
First a few words about the ViewModel
in android MVVM architecture.
The important thing to remember is that the constructor of ViewModel cannot have any arguments.
So we cannot directly use constructor injection on ViewModels. What we can do instead is to somehow let Dagger provide the ViewModelProvider.Factory
.
Actually, we will create a very clever implementation of the ViewModelProvider.Factory
interface and let Dagger provide that.
public class ViewModelProviderFactory implements ViewModelProvider.Factory {
private static final String TAG = "ViewModelProviderFactor";
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelProviderFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) { // if the viewmodel has not been created
// loop through the allowable keys (aka allowed classes with the @ViewModelKey)
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
// if it's allowed, set the Provider<ViewModel>
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
// if this is not one of the allowed keys, throw exception
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
// return the Provider
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Notice that this class is using constructor injection. And the only dependency it has is a map. The map from -> Provider. So when we use this Factory
, it will automatically look up the corresponding ViewModel provider to create the ViewModel object.
There are now 2 questions:
- Where does this map come from?
- How each ViewModel is created?
Let’s answer the 2nd question first. This is simple, just use normal constructor injection in ViewModel classes.
public class AuthViewModel extends ViewModel {
private static final String TAG = "AuthViewModel";
@Inject
AuthViewModel() {
Log.d(TAG, "AuthViewModel: is working!");
}
}
The anwser of the 1st question needs more explanation. In short, this map (vm class -> Provider) can be created using multi-binding.
// /di/auth/AuthViewModelsModule
@Module
public abstract class AuthViewModelsModule {
@Binds
@IntoMap
@ViewModelKey(AuthViewModel.class)
public abstract ViewModel bindAuthViewModel(AuthViewModel viewModel);
}
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
Class<? extends ViewModel> value();
}
We then add this AuthViewModelsModule
into the AuthActivity
subcomponent. Wait, we did not create any subcomponent!?
Actually by using @ContributesAndroidInjector
, it will automatically create a subcomponent for us.
So let’s update the ActivityBuildersModule
to the following:
@Module
abstract class ActivityBuildersModule {
@ContributesAndroidInjector(modules = {AuthViewModelsModule.class})
abstract AuthActivity contribute();
}
Next we can create a @Module that binds our clever ViewModelProvider.Factory
:
// /di/ViewModelFactoryModule
@Module
public abstract class ViewModelFactoryModule {
@Binds
public abstract ViewModelProvider.Factory bind(ViewModelProviderFactory factory);
}
And add it to AppComponent
.
Now we can try to inject the AuthViewModel
in AuthActivity
.
public class AuthActivity extends DaggerAppCompatActivity {
private AuthViewModel viewModel;
@Inject
ViewModelProviderFactory viewModelProviderFactory;
@Inject
Drawable logo;
@Inject
RequestManager glideInstance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
viewModel = new ViewModelProvider(this, viewModelProviderFactory).get(AuthViewModel.class);
setLogo();
}
private void setLogo() {
glideInstance.load(logo).into((ImageView) findViewById(R.id.login_logo));
}
}
The code for this article can be found here
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Email