ViewModel DI with Dagger

rainbow-cake-dagger

This page describes the setup used by the rainbow-cake-dagger artifact, an optional Dagger integration of RainbowCake.

One Factory to rule them all

ViewModels can’t be directly instantiated, as we need the framework to take care of their lifecycle management (to retain them across configuration changes). The way to give a ViewModel a custom constructor is by using a custom ViewModelProvider.Factory. These factories look something like this, they receive the ViewModel class as a parameter when they need to instantiate one:

class MyViewModelFactory : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return  // ...
    }

} 

Inside this method, we have complete freedom to manufacture our ViewModel instance, so we’ll make Dagger do it, gaining the ability to inject the ViewModels the factory creates via their constructors so that they can just “ask for” dependencies there and not worry about where they come from. We can also use a single factory powered by Dagger which can instantiate any type of ViewModel that Dagger can provide. This is where the multibinding mechanism comes in.

Using multibindings, we construct a map in our ViewModelModule. The keys in the map are the Class values of each ViewModel (e.g. UserViewModel::class). The values in these map are the ViewModel types themselves (e.g. UserViewModel).

This may seem redundant, but keep in mind that generally speaking, when using multibindings the keys we label our dependencies with could be other types as well, for example simply String or Int. We’re only using Class as the key here because that’s what the ViewModelProvider.Factory implementation will receive as a parameter when it has to instantiate a ViewModel. This will essentially let us ask Dagger at any time to create a ViewModel of a given Class.

@Module
abstract class RainbowCakeModule {

    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory)
        : ViewModelProvider.Factory

}

We’ve also bound the DaggerViewModelFactory we’re implementing, so that we can inject the map we’ve constructed above into it with Dagger.

We could simply ask for a Map<Class<ViewModel>, ViewModel> in the Factory, but then we’d only get one concrete instance of ViewModel for each key (essentially, for each screen). If we opened the same screen multiple times, they’d have to have the same state because they’d receive the same ViewModel instances. This makes no sense (except for some rare cases).

Instead, we’ll ask for the Provider<ViewModel> type as the map’s values. A Provider is a class we can keep calling get() on, and each time it will manufacture new ViewModel instances, which will have all of its dependencies injected by Dagger appropriately.

This brings us to this implementation for our ViewModelProvider.Factory:

class DaggerViewModelFactory @Inject constructor(
        private val creators: Map<Class<ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return creators[modelClass].get() as T
    }

}

Let’s review one more time:

  • It receives a Map in its constructor from Dagger, which is backed by Dagger’s injection mechanism to create any ViewModel that’s been bound with its own Class as the key.
  • Whenever we invoke ViewModelProviders and pass this Factory to it, it’ll call the Factory with the concrete ViewModel Class we need to create.
  • This Factory will fetch the appropriate Provider from the Map it stores in its creators property, and create a new instance of the required type of ViewModel by calling the Provider’s get method. This ViewModel will receive its dependencies via constructor injection, because it’s instantiated by Dagger.

Here’s how these components are (roughly) connected:

The Dagger powered ViewModel factory

The code for DaggerViewModelFactory here is sample code that doesn’t even quite compile, but I hope it gets the idea across. There’s a couple more little things you need to take care of for this implementation to work in terms of generics, Kotlin-Java interop, and null handling. If you want to see the real, complete implementation, look at the DaggerViewModelFactory class of the framework.

Accessing the Factory

We still need to get hold of an instance of this Factory in our Fragments so that we can fetch ViewModels with its help. More specifically, this will be done by the getViewModelFromFactory helper function that the Fragment will call into to get a ViewModel.

First, the Factory is exposed by the app level Dagger component, RainbowCakeComponent:

interface RainbowCakeComponent {
    fun viewModelFactory(): ViewModelProvider.Factory
}

Then, in getViewModelFromFactory, we simply get a hold of the RainbowCakeApplication via the Fragment’s context, and grab the Factory from its component:

val viewModelFactory = (getContext()?.applicationContext as? RainbowCakeApplication)
            ?.injector
            ?.viewModelFactory()

We could now fetch our ViewModel in this method like this, and if it needs to be created, it will be created by Dagger somewhere in the Factory:

ViewModelProvider(this, viewModelFactory).get(VM::class.java)

The getViewModelFromFactory method performs essentially this same call, with some extra handling for shared ViewModel scopes.

References

For more about Dagger bindings and multibindings, see here and here, for example.

The ViewModelProvider.Factory implementation is originally from a Google sample project, specifically, from their GitHub Browser Sample app.