This page describes the setup used by the
rainbow-cake-dagger
artifact, an optional Dagger integration of RainbowCake.
ViewModel
s 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 ViewModel
s 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:
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.ViewModelProviders
and pass this Factory
to it, it’ll call the Factory
with the concrete ViewModel
Class
we need to create.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 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.
We still need to get hold of an instance of this Factory
in our Fragment
s so that we can fetch ViewModel
s 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.
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.