Android ViewModel injection with Dagger
ViewModel from Android Architecture Components is a long-awaited solution from Google to address android application architecture, and it packs some really nice features too. A fine new addition to the set of essential android development tools that we use here at Chili (alongside Dagger, RxJava, Data Binding Library). But making the new kid to play nice with the others is not always as easy as it seems.
The structure of this article reflects my thought process (with questions and ideas that arose along the way) and results in a final solution of how to make ViewModel work well with dependency injection using Dagger.
You should be familiar with Dagger for Android to understand the basic setup and some behind the scenes actions.
An android application that uses:
- Standard Dagger setup (which generates subcomponents for it’s fragments to use with AndroidInjection), with one exception: since fragments are used as independent pieces of functionality, they are injected from Application class directly (implements HasSupportFragmentInjector). This gives us an opportunity to inject after fragment super.onCreate(savedInstanceState) call. Which is crucial to allow android to retrieve previously created ViewModel before we will try to inject it.
- A ViewModel that requires a new instance of some expensive object to be constructed.
Ideally, we would like for Dagger to deal with the creation and injection of a ViewModel instance.
First, let’s outline the core concepts.
Lifecycle and scope
ViewModel instances are retained on fragment recreation. This makes their scope span from the moment user enters a part of your application (provided by this fragment) and until user leaves it. During this scope (lets call it “user scope”) fragment can get recreated more than once due to configuration changes (i.e. device rotation). For such scenarios, ViewModel is a good place to keep the state.
Every time a new fragment is created (even if it was just recreated due to configuration change), we perform dependency injection. Under the hood, it’s done by creating a new subcomponent, that was generated for this fragment, and using it to provide dependencies. Subcomponent is then discarded and we have no way of keeping it for later reuse. As a result, there is no sane way of a subcomponent being scoped with our “user scope”, meaning we can’t simply replace ViewModelProviders with scoped Dagger providers for our ViewModel needs.
A workaround is for Dagger to provide ViewModelProvider.Factory instances. They will contain all required dependencies for ViewModel creation. Although, during a single “user scope” a new factory instance will be injected each time, the actual ViewModel instance will only be created once, by the first factory that was provided.
Looks nice, but there is a problem. Dependencies required to create ViewModel are injected into factory as concrete instances. Some of these dependencies might be expensive to create, and since only one factory instance will actually be used during a single “user scope”, this means that we are wasting resources on objects that will never be used. We can minimise the impact by using injecting factory with providers instead of concrete instances. This way dependencies for ViewModel will be provided only once, when instance is created. Even cleaner, we can define a provider method for a ViewModel, and use generated provider as in factory.
Now we have a nice and clean way to define ViewModel creation in a Dagger module. No instances of ViewModel or any of it dependencies will be provided until a factory requests it. So during the recreation and injection we only waste Factory object instances.
Singleton abstract factory
We know that the scope of a ViewModel is larger than that of a fragment subcomponent. It means that we should add a module responsible for providing ViewModel factory to parent component instead of fragment subcomponent. This will make all provided factories in the project to have the same scope in the same component, as the result, we can combine them all into a singleton factory. To accomplish that, we can use Dagger multibindings.
We no longer create new instances of factories on injection, which means we don’t produce any waste.
Now, we can make a final change and really tuck all implementation into the modules. The subcomponent (that is generated using android injection) binds instance of the target fragment. This allows us to use this fragment’s instance for injections within the scope of the subcomponent. We can add a new module to our subcomponent and move the call from the fragment to the ViewModelProviders.
Finally, all logic for ViewModel creation is moved to Dagger modules.
And that’s about it. Comments and suggestions are welcome. Feel free to share your thoughts or solutions about this topic and also ideas about what you would like to read in my next post.
Thank you for reading :)
UPDATE: Example project showcasing this approach https://github.com/ChiliLabs/viewmodel-dagger-example