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.

If you haven't read about the latest Android Architecture Components libraries, the MVVM pattern and the ViewModel class yet, please read it first as I don't explain it here.

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.

The Google's 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.

{: .center-image}

Injectable ViewModels

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 (not any more - read the note below), 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.

Another note: if you would like to read about a newer solution for injecting ViewModels which I find better, click here.

Simple factory

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.

The ViewModelProvider.Factory interface defines only one method:

@NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass);

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 Provider s 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 Component s, Provider s, Factory s etc. The Provider is an object which provides the instances of some class ( Factory is also a Provider ).

The MapKey annotation (docs) lets you generate a map of objects provided by Dagger or the Provider s 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<android.arch.lifecycle.ViewModel>> 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 Provider s 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:

import android.arch.lifecycle.ViewModel import dagger.MapKey import kotlin.reflect.KClass @MustBeDocumented @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @MapKey annotation class ViewModelKey(val value: KClass<out ViewModel>)

And then we can use it in a module like below.

import android.arch.lifecycle.ViewModel import com.azabost.simplemvvm.ui.main.MainViewModel import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap @Module abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(MainViewModel::class) abstract fun bindMainViewModel(mainViewModel: MainViewModel): ViewModel }

@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 KClass into Class for Java compatibility.

Note: you may want to read the Binds docs, IntoMap docs and multibindings docs.

Using the map in the factory

The view model factory which uses the generated map will be as simple as this:

import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton @Singleton class InjectingViewModelFactory @Inject constructor( private val viewModelProviders: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { val provider = viewModelProviders[modelClass] ?: viewModelProviders.entries.first { modelClass.isAssignableFrom(it.key) }.value return provider.get() as T } }

And we can also add it to the same Dagger module:

import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModelProvider import com.azabost.simplemvvm.ui.main.MainViewModel import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap @Module abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(MainViewModel::class) abstract fun bindMapViewModel(mapViewModel: MainViewModel): ViewModel @Binds abstract fun bindViewModelFactory(factory: InjectingViewModelFactory): ViewModelProvider.Factory }

Obtaining view models

In the activity you can now inject the factory:

@Inject lateinit var vmFactory: ViewModelProvider.Factory

and use it to obtain the view model:

val vm = ViewModelProviders.of(this, vmFactory).get(MainViewModel::class.java)

Don't forget to annotate your view model's constructor with @Inject :

class MainViewModel @Inject constructor( private val gitHubClient: GitHubClient ) : ViewModel(), MainVM, LoadingVM, DataVM { ... }

If it seems too complicated to you, please take a look at the diagram below. It may help you to see the big picture.

{: .center-image}