MVVM Architecture for Android consuming Marvel API Part 2: Dagger2 and Dependency Injection
In the first part of the series we talked about MVVM design pattern, and how its different parts interact between each other. We also explored how to configure Room in your project.
In this part I’ll explain how to use Dagger2 to provide dependencies in our application. I highly recommend that you read dagger 2 tutorials and understand what dependency injection is, otherwise it could be pretty hard to understand this article.
Every piece of code is in this sample:
Interaction Diagram
Let’s see which our main parts are and how they interact between each other, then I’ll elaborate further.
We mainly have two modules which provide our dependencies: AppModule and NetModule. The first one provides the instance of our database, and its Daos. This module could provide some other main dependencies like the Preferences. The other one, NetModule, provides everything linked to the Network (Retrofit instance, Interceptor, Authenticator, MarvelApi etc.).
In the other 3 modules, we basically define where we want to bind our dependencies (Activities, Fragments and ViewModules), thus where we need these dependencies.
Finally, our Application class will build AppComponent.
We’ll see them detailed in the next section…
AppComponent
A class annotated with @Component is a bridge between @Module and @Inject. It’s a common practice to create a main component called AppComponent which provides the main dependencies. This component is the root of our dagger graph and it provides different modules. Let’s have a look:
Before describing the modules, I’ll explain some annotations used here. Components can be instantiated by using the Builders generated by Dagger, this was the old way:
DaggerAppComponent appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this)) //this : application
.netModule(new NetModule())
.activityModule(new ActivityModule())
...... .build();
Now we can customise the generated builder by utilising something known as Component.Builder.
A builder for a component. Components may have a single nested static abstract class or interface annotated with
@Component.Builder
. If they do, then the component's generated builder will match the API in the type
So using it, Dagger generates the same class Builder as before! We can then customise this Component.Builder
using @BindsInstance.
@BindsInstance
methods should be preferred to writing a@Module
with constructor arguments and immediately providing those values — source
To understand it properly, let’s do an example with AppModule which will provide the main dependencies of our Application, like Preferences, Room etc and it used to provide the Application Context provided as constructor arguments :
public AppModule(Application application) {
this.application = application;
}
Let’s look at our simplified AppModule
with the constructor and @Provides Application
removed:
So... where is the application instance?? If you look the AppComponent class, you’ll notice that it’s provided by the BindInstance!
@BindsInstance
fun application(application: Application): AppComponent.Builder
We don’t need to specify Builder appModule(AppModule appModule);
inside the Component.Builder,
as we are going to let Dagger use the default constructor of AppModule
now. So, when we instantiate the AppComponent inside the Application class, it will be like this:
DaggerAppComponent.builder().application(this).build().inject(this)
That’s it, we’ve just seen our AppComponent, some annotations with their meanings and effects (Component.Builder , BindInstance), the AppModule and how to instantiate our component.
AndroidSupportInjectionModule: I didn’t create this, it is an internal class in Dagger 2.10. It provides our activities and fragments with a given module.
ActivityModule
If you’ve used Dagger before probably you noticed that it introduces a lot of boilerplate code, especially when we create subcomponents for each Activity, Fragment, Service etc. Dagger has introduced an annotation which reduces it, it’s @ContributesAndroidInjector and attaches activities and fragments directly to your dagger graph. So, for example, if we have HomeActivity and we want to generate a HomeSubComponent, we’ll write this line of code inside a module:
@ContributesAndroidInjector
internal abstract fun contributeHomeActivity(): HomeActivity
It’s good practice to create a module to handle all such cases and include it in our modules, and this is what Idid using ActivityModule:
Another improvement using this annotation is about how we inject and use the components inside Activities, Fragments etc. For example, we used to create them directly inside an Activity, then we managed them using SavedInstanceState etc. A class should never know how it is being injected.
Thus, let’s see how our Activity was before, and how it has been changed using @ContributesAndroidInjector:
The final things to know about this amazing annotation are provided by the official documentation:
- “Generates an AndroidInjector for the return type of this method” : It will generate
AndroidInjector<HomeActivity>
. - “This annotation must be applied to an abstract method in a Module that returns a concrete Android framework type.” i.e. Activity / Fragment, etc.
FragmentModule
Here it’s the same but with Fragments, Dagger attaches them to Dagger Graph always using ContributesAndroidInjector.
For Fragments, we initialise our dependencies in onAttach()
@Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
ViewModelModule
To understand this last module and what we’re doing here, I’ll try to explain how we can use Dagger2 multibindings with ViewModel.
For building customViewModel
classes with argument-passing constructors (e.g. for passing custom data or @Inject
annotated constructors), we must provide a class that extends ViewModelProvider.Factory
, returning instances of our custom ViewModels into the create()
method. This class is AppViewModelFactory:
Let’s try to understand this class and what it does. The first thing is the constructor which takes Map<Class<? extends ViewModel>, Provider<ViewModel>>
as parameter. It’s a map which has a Class
that extends ViewModel
as key, and a Provider
of ViewModel
(a Dagger 2-specific class that let us to provide — and so instantiate — a dependency-injected class) as a value. Thus, a Provider
can give us the ViewModel
of a determinate type.
How can we use this Provider and where ?
The answer is the create
method of our custom ViewModelProvider.Factory
, which will return an instance of the ViewModel. This method takes the type T of the ViewModel, that was requested from an Activity
or Fragment,
as parameter. If inside our Map
we have a Provider which can instantiate a ViewModel of type T, then we can instantiate and return that class to the system. Otherwise we return “unknown model class”. In this line of code, we’re retrieving the Provider of a given type, where modelClass is the parameter of create
method:
var creator: Provider<out ViewModel>? = creators[modelClass]
If our Provider
map hasn’t got that specific key, we will check if there is a subclass of the ViewModel
:
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
If we then finally find it, we can instantiate it by invoking the get method on the Provider
object, and casting it to type T:
return creator.get() as T
Now that we’ve understood how this class works, another question could be where are we providing the parameters of this class ??
The answer is inside ViewModelModule. How? Always through some annotations. Let’s see them and then I’ll elaborate:
Dagger 2 can associate a Provider
to a given type, and inject it into a Map. This is possible using @IntoMap
on a method that provides a value with a given type, and in our case it provides a Class which extend ViewModel. The key is provided by a custom annotation @ViewModelKey
, annotated using @MapKey
, where we’re specifying as type of our key KClass<out ViewModel>
:
So we’re injecting this object into a Map
( @IntoMap
annotation) using HomeActivityViewModel.class
as key, and a Provider
that will build a HomeActivityViewModel
object (the parameter of the @Binds
annotation) as value”. In this way, we can inject into a Dagger 2 managed object, a map of type of our famous Map<Class<? extends ViewModel>, Provider<ViewModel>>
.
Conclusion
In this article I’ve tried to explain how we can handle the dependencies using some recent annotations introduced by Dagger 2 and how can we reduce the boilerplate code. In the next one, I’ll show you why this way of structuring the dependencies is so useful.
Thanks for reading, if you enjoyed it, click on the applause button to help other people find it.
Feel free to get in touch with me on Linkedin.