Android Developers team finally @Provides opinion about dependency injection and the winner is 🥁 🥁🗡️ Dagger 🗡 🏆. From my perspective, Dagger is “not great, not terrible”, but it scales well and when properly set up, you don't need to “touch” it later.

In this article, I'll try to explain how to setup Dagger to work with ViewModel and SavedState module in the most universal way — set it once, use for ever.

WARNING:

I’m not going deep into Dagger, so if you don’t know the basics of how Dagger works, please check it, otherwise the article won’t be understandable 😒.

TLDR:

I'm sorry — the article is longer than I expected, but I wanted to explain it with sufficient amount of information. For those who are familiar with everything, but want to see working snippets only (or you're lazy enough), just go ahead to the TLDR section.

Fasten your seatbelt, it's gonna be rough ride.

The Motivation

Let's start with some motivation about why do we want to do all this?

Let's say you have some ViewModel class SomeViewModel : ViewModel() . Now, you want to fully use power of ViewModels, so you apply inversion of control and pass dependencies to the constructor.

You're even more demanding and you have DI framework (Dagger in our case) to do this for you. I don't describe here, how to setup Dagger with ViewModel as there are many articles and SO answers available (e.g. here or here).

You still want more? Outrageous! You actually want to pass something besides dependencies coming from DI graph. Something like Bundle , or just some variable like articleId .

What are your options?

You can instantiate your ViewModel with dependencies from graph and set your custom variable manually after construction with either lateinit var or with var of nullable type.

Neither of these is great, because it makes your code more fragile — what if you reuse your ViewModel in other screen and forget to set the dynamic parameters — crash 💥 … or not properly initialised class 🤪. And in case of nullable variable, either you force !! it, or you have unnecessary null checks in your code 🤷‍♂.

Another option is to leave Dagger out of the game, create custom ViewModel factory and manually pass dependencies which will be injected into Fragment or Activity 🤢.

In Fragment, you need to @Inject the dependencies and pass it to the factory.

This works, but it’s so much boilerplate 😢. With each added, changed or removed dependency, you have to update three places in your code:

ViewModel’s constructor

ViewModel's custom factory

instantiation of the factory with injected dependencies.

(Un)fortunately this is exactly, what we want to achieve (possibly) without all of the boilerplate, because we want to use one dynamically retrieved parameter — SavedStateHandle from Saved State Module library.

ViewModel and onSaveInstanceState()

Before we dive into Saved State module, let's recap ViewModel's strength and weakness.

ViewModel is great at handling orientation change as it survives when Fragment or Activity is destroyed and therefore you can keep doing any long action without leaking the screen or need to “restart” the action.

On the other hand, when you put your app into background and Android system kills it (usually due to longer inactivity and need for more resources), the state of ViewModel is not preserved. What's worse, Fragment or Activity will handle this use case by calling onSaveInstanceState(outState: Bundle) , but your ViewModel has no information about it. You need to handle this yourself by taking data from ViewModel and saving it in Fragment/Activity and later restoring it by manually placing into ViewModel. But as I showed earlier, this makes your ViewModel less robust, because this way you can't run actions from constructor.

ViewModel doesn’t handle saving/restoring state

The truth is, many apps don’t even bother to solve this issue, which leads to weird behaviour or even crash when user opens the app after some inactivity.

There's light 🔦 at the end of the tunnel though.

Saved State Module for ViewModel

Saved State Module for ViewModel is new AndroidX library which allows handling instance state from ViewModel without any difficulty. This library provides custom factory to create ViewModels with. ViewModel constructor then expects SavedStateHandle parameter which it communicates with. Destruction (and saving instance state) of a Fragment/Activity is then reflected in the handle and therefore ViewModel can save or restore its state without help of other classes.

How to use it then?

0

Add gradle dependency.

implementation

"androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03"

1

Get the ViewModel with SavedStateViewModelFactory factory. You may also specify some default arguments to be passed into ViewModel. Parameter this in the constructor is SavedStateRegistryOwner which is either Fragment or Activity and serves as reference for saving/restoring the instance state.

2

In your ViewModel’s constructor, have variable of type SavedStateHandle , which serves as handler for saving or retrieving data. If you passed any default arguments, they will be part of this handle.

The handle has set/get methods similar to Bundle which, in case of ViewModel being killed by the system, are safely stored in app's state and restored later. It also has getLiveData(key) method which returns MutableLiveData to simplify working with UI.

In example below, counter value is retrieved in ViewModel's init{} . If value is not set, null is returned. Method onPlusClick() changes counter LiveData. In the end, you observe the LiveData and set current value into the handle, which will be safely stored.

You can also simplify it by using getLiveData(key, defaultValue) method which results in the same functionality with less code.

This way, when system kills your app, you can be sure that ViewModel will save and restore its state properly without help of Fragment/Activity class.

How to test system killing your app? 🔫

With all being said now, you're thinking — how do I know if it actually works Often you don’t want to sit and wait until Android system kills your app in background and you want to test it reasonably. There are two easy options how you can simulate the behaviour:

Set background process limit to none:

On your device go to Settings ➡ Developer options ➡ Background process limit ➡ set to No background processes Kill your app with adb:

- Put your app into background (if you don’t do this step, adb will kill the app anyway, but system won’t save instance state)

- Use the adb command from snippet to kill your package. Small shell script may help to kill it for you and save some time 💪🏻