Injectable Android ViewModels
In the following post I’m going to show a simple (almost boilerplate-free) yet powerful implementation of the view models dependency injection on Android using Dagger.
The Android weaknesses
I think the fact that Google has decided to help developers by creating their own library for supporting the MVVM pattern on the Android platform was a really good move.
A few months before its release I was wondering what was the best approach to introduce the MVP or MVVM pattern on Android. I have tested or reviewed a few libs on GitHub and none of them seemed good enough for me - either due to their limitations or a huge amount of boilerplate code they required. Also the way they were implemented and the number of reported bugs were not encouraging.
I was envious of the simple approach the iOS developers have - they can instantiate a view controller on their own and pass a view model directly to it (e.g. via the constructor). The iOS system also doesn’t kill the view controller when the screen orientation changes so it doesn’t have to obtain the view model again and, at the same time, the view model gets destroyed with the view controller when it’s not needed anymore (provided that you don’t hold another reference to it).
But on Android you start with an activity component and you can’t prepare the view model outside of its lifecycle easily. Also storing the reference to the view model only in the activity which gets destroyed on every configuration change (like screen orientation) will destroy the view model as well and it’s not convenient. If you would like the view model to survive, you would have to hold a reference to it somewhere else but then another problems arise e.g. how to clean the view model when it’s not needed anymore or how to make every activity of the same class to use a different view model instance.
ViewModel was designed to help with such issues. Unfortunately, it still needs to be created during the activity lifecycle but with several Dagger tweaks you can easily inject any view model’s dependencies to it.
Before we begin to play with the code I wanted to add that I have googled other people’s approaches to view model injections and I didn’t like them too much because of the significant amount of the boilerplate code (e.g. writing a separate view model factory per view model). The best one, which my example is based on, comes from the Google’s samples repository. I have simplified some parts of it and rewritten it in Kotlin.
Note: you can access the whole code used in this example on GitHub.
The default library’s factory instantiates view models using empty constructors. Of course, we can’t use it as we are going to create the view models with non-empty constructors, passing the dependencies obtained from Dagger.
ViewModelProvider.Factory interface defines only one method:
As you can see, it takes the class of a view model and it must return its instance.
In order to use a single simple and universal factory (which is the main point of this post) for all the view models we need to create a map of
Providers for every view model class. While I was analysing the mentioned Google’s sample I didn’t know how the map generation works and it wasn’t very easy to understand so I’m going to exaplain it a little more here to save you the trouble.
Generating the map
If you have already used Dagger, you might have also noticed the code it generates. Most of that code are the
Factorys etc. The
Provider is an object which provides the instances of some class (
Factory is also a
MapKey annotation (docs) lets you generate a map of objects provided by Dagger or the
Providers of those objects. In our example we will need the following map:
Map<Class<? extends ViewModel>, Provider<ViewModel>>
which can be translated to Kotlin as:
Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
Note: Dagger generates Java sources and that’s why we must remember about the variance differences between Java and Kotlin (generics docs, Java to Kotlin interoperability docs) which could be troublesome if you don’t use the
@JvmSuppressWildcards annotation, resulting in this error:
error: [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider
> cannot be provided without an @Provides-annotated method.
This map’s entries consist of a key - a class of any view model and a value - a
Provider of any view model. Obviously, we must feed the map with the corresponding
Providers for every view model, e.g.
ViewModelA -> Provider<ViewModelA>. With such a map the factory will be able to easily return an instance of any view model with all its dependencies fulfilled by Dagger.
In order to generate the map we need two elements: a map key definition and a module with view model bindings.
The map key definition is an annotation type which has a single member whose type is the map key type. It may look like this:
And then we can use it in a module like below.
@Binds methods are a drop-in replacement for
@Provides methods that simply return an injected parameter. Combining it with
@IntoMap and our key (
@ViewModelKey) will put a provider of the returned object into the map under the key specified by the key annotation’s parameter. In this case the
Provider<MainViewModel> instance will be put under the
MainViewModel::class key. Kotlin will also translate the
Class for Java compatibility.
Using the map in the factory
The view model factory which uses the generated map will be as simple as this:
And we can also add it to the same Dagger module:
Obtaining view models
In the activity you can now inject the factory:
and use it to obtain the view model:
Don’t forget to annotate your view model’s constructor with
If it seems too complicated to you, please take a look at the diagram below. It may help you to see the big picture.