Updated Dagger integration to recommend using Dagger Hilt: Dependency Injection, which greatly simplifies dependency injection in ViewModels. Updated to Lifecycle ViewModel SavedState version 2.2.0.

Check out my sample Github repository for a complete working example of everything I discuss here.

Saved State module for ViewModel is a new library from Google that builds on top of ViewModel architecture component. It let's you restore the UI state after a process stop.

In this article, I'm going to walk you through the basic usage of this library and continue deeper into a more advanced use case of integrating it with Dagger:

Saving UI State — The Challenge

One of the main challenges in Android development is persisting the state of the Activity/Fragment when a configuration change occurs. Android Architecture Components features the ViewModel component to address this.

Nevertheless, a configuration change is not the only behavior that results in Activity/Fragment losing its state. The state can be lost also when the system runs out of resources and has to stop the app’s process while it’s in the background. The easiest way to reproduce such behavior is to enabled Don’t Keep Activities in Developer Options of your test device. By doing so, the Activity will be destroyed whenever you leave it. ViewModels alone do not cover this case and there’s a very good article about this by Lyla Fujiwara, which I encourage you to read.

To summarize Lyla’s article, the only way to address this case is by overriding onSaveInstanceState(Bundle) in the Activity/Fragment and persist the state in the passed Bundle . If what you want to persist is in the ViewModel, this requires communication between ViewModel and Activity/Fragment. One way would be to introduce getInstanceState(): Map<String, Object> and setInstanceState(state: Map<String, Object>) in the ViewModel for setting and getting the state to/from the ViewModel when the Activity is destroyed/recreated. A major drawback of this approach is that now the state of the ViewModel is deferred to specific lifecycle events.

Note that you shouldn’t use this method for persisting large objects (the limit is 1MB), but only the bare minimum required to reconstruct the UI to its previous state. Otherwise, you may encounter TransactionTooLargeException.

Introduction to Saved State module for ViewModel

Google has released a new library called ViewModel SavedState, which is an addition to ViewModel to support Activity/Fragment state persistence through process stop. The way it works is by passing a SavedStateHandle to the ViewModel’s constructor, which you can then use to restore/save the state.

We start by including the necessary Gradle dependencies and creating the ViewModel:

app/build.gradle :

handle: SavedStateHandle is just a map. You can query it for its content and store the state in it like you'd normally do with a Bundle passed to onSaveInstanceState() . Note that to store complex objects, you have to implement the Parcelable interface.

Pro Tip: Kotlin’s Android Extensions has a neat feature that implements the Parcelable interface for you by annotating your data classes with @Parcelize . Read more here.

To get an instance of DetailViewModel in our Activity, we will use the new extension function viewModels included in androidx.activity:activity-ktx library, which returns a Lazy delegate to access the Activity’s ViewModel:

ViewModel with constructor arguments

If you’re already using ViewModels in your project, chances are that you’re passing other arguments to the ViewModels’ constructors via your own custom ViewModelProvider.Factory so let’s see what are the options we have in this case.

SavedState module ships with an abstract factory called AbstractSavedStateViewModelFactory. This class requires us to pass an instance of SavedStateRegistryOwner and nullable Bundle as the default state.

Let’s add another argument, githubApi , to the ViewModel’s constructor:

The ViewModel above calls the Github API with the ID we stored previously in SavedStateHandle .

Once again, to get this ViewModel instance we will use viewModels extension function and pass an implementation of AbstractSavedStateViewModelFactory that knows how to construct this ViewModel and pass the required dependencies.

And then hook it up to the Activity:

We’re passing a new instance of DetailViewModelFactory to viewModels extension function. The factory takes an instance of GithubApi and SavedStateRegistryOwner instance as a 2nd argument, which is in this case, the Activity. This 2nd argument is very important since it ties the saved state to the current component (either Activity or Fragment). In case you’re creating a shared ViewModel among Fragment s, you should pass the host Activity instance here and use activityViewModels extension function instead of viewModels .

Note that a factory is needed for each ViewModel that takes SavedStateHandle as a constructor argument.

Integrating SavedState ViewModel with Dagger and Hilt

This section talks about how to inject ViewModel factories with Dagger, which results in a lot of boilerplate code that can be completely avoided by using Dagger Hilt: Dependency Injection for Android built on Dagger to replace Dagger Android. When using Hilt, you won’t need any ViewModel factories anymore since that’s already being taken care of by Hilt. Please refer to the official guide on how to add this support. Note that you must already be using Hilt. If you’re still using plain Dagger or Dagger Android and curious to see what I mean, check out this PR that migrated from Dagger Android to Hilt. You’d be amazed how much code has been removed.

The traditional ViewModel factory with Dagger looks like this:

In the code snippet above, we have a ViewModelProvider.Factory implementation that is injected with a map of ViewModel class types and their Provider s, which will then create a new instance of the required ViewModel by calling Provider.get() with all the dependencies injected to the ViewModel’s constructor.

Next, we utilize Dagger’s MultiBinding feature to construct the map by binding each ViewModel class type:

As you can see in the following code snippet, we now annotated DetailViewModel 's constructor with @Inject so that dagger will perform constructor injection and supply the dependencies.

You may have noticed that we have a problem now.

First, we need to extend AbstractSavedStateViewModelFactory and not ViewModelProvider.Factory

and not and second, the we have a new ViewModel constructor argument, SavedStateHandle , but we only get its instance when AbstractSavedStateViewModelFactory calls our implementation of create() function where it then passes the SavedStateHandle instance. With the traditional Dagger setup, we get the ViewModel instance from Provider<ViewModel>.get() and there’s no option to pass extra constructor parameters so this approach with Dagger is not going to work anymore. We’ve already seen earlier that the only way to solve this problem is by having a ViewModel specific factory.

constructor argument, , but we only get its instance when calls our implementation of function where it then passes the instance. With the traditional Dagger setup, we get the instance from and there’s no option to pass extra constructor parameters so this approach with Dagger is not going to work anymore. We’ve already seen earlier that the only way to solve this problem is by having a specific factory. Moreover, AbstractSavedStateViewModelFactory takes SavedStateRegistryOwner , which if you recall, is a very important argument that associates the saved state to the Activity/Fragment. We cannot just inject whatever Activity/Fragment instance here.

Unfortunately, the traditional approach is no longer valid when using SavedState ViewModel. As mentioned previously, you have to create one Factory per ViewModel while making sure that you pass the right SavedStateRegistryOwner instance.

Nevertheless, we can still use Dagger to inject other dependencies needed for the ViewModel by creating 2 layers of factories — covered in the next section.

Injectable factory per ViewModel

Create an interface that each ViewModel factory will need to implement:

Now create a factory per each ViewModel and implement the interface. Make sure to make this factory injectable so that Dagger will inject all the dependencies:

Notice that we don’t annotate the ViewModel constructor with @Inject since we the factory will create it.

Implementing AbstractSavedStateViewModelFactory for handling multiple ViewModels

With the above generic implementation, the factory can create any ViewModel since it takes the factory interface we created earlier.

Now we hook it up to the Activity by first injecting the ViewModel factory and then using viewModels extension function while passing AbstractSavedStateViewModelFactory implementation, which takes the specific ViewModel factory and the current Activity instance as a SavedStateRegistryOwner .

Check out my sample Github repository for a complete working example of everything I discussed here. Note that master branch of this repository has already migrated to Hilt. In case you’d like to inspect the code prior to the migration, please check out this branch instead. If you liked this article, please remember to clap and follow me here on Medium and on Twitter for more articles in the future.

⚠️UPDATE v2.2.0: The article ends here. The next section was written when the library was in alpha stage and is outdated. It talks about Assisted Inject library for Dagger, which I don’t recommend to use anymore with SavedState ViewModels for the reasons I listed earlier — mainly because it’s very tricky to control what instance of SavedStateRegistryOwner is injected by Dagger or even impossible to inject at all if you’re using FragmentFactory due to cyclic dependency, so I just recommend to completely avoid it unless you’re an adventurer 😄

Helping Dagger Help You

This subtitle was adopted from Jake Wharton’s talk Helping Dagger Help You about Dagger Assisted Inject at DroidCon UK.

IMPORTANT: THIS SECTION IS OUTDATED AND IS NOT RECOMMENDED ANYMORE!

We need to assist Dagger to construct the ViewModel by passing the instance of SavedStateHandle we get from AbstractSavedStateVMFactory.create() . To do that, we’ll use AssistedInject as an add-on library to Dagger. AssistedInject generates factories which we can then use to construct a map of ViewModel types and their factories by utilizing Dagger’s MultiBinding feature as we’ve done before.

First, let’s include the Gradle dependencies:

app/build.gradle :

With AssistedInject we can tell Dagger which constructor argument we want to assist with by annotating the argument with @Assisted annotation and instead of using @Inject , to inject the ViewModel , we will use @AssistedInject .

The next step is to make AssistedInject generate the factory for this ViewModel . We need to create a factory interface that takes the argument(s) that we want to assist with and returns an instance of the ViewModel .

Since we want to support many different types of ViewModel factories, we create a generic factory interface as shown above. We then extend this interface per ViewModel and return the type of the ViewModel we want to return:

Note that the factory interface above must be nested inside the ViewModel class otherwise you will get a compilation error.

If we build our project now, we will have a factory named DetailViewModel_AssistedFactory generated for our ViewModel . Next we will need to get these generated factories into our dependency graph. AssistedInject can generate a Dagger module for us so we can then just include it in one place:

We create an empty Dagger module and annotate it with @AssistedModule . We then build to get the factories module, AssistedInject_ViewModelAssistedFactoriesModule , generated and include it in Module(includes = [..]) as shown above. Now, include this module in your ApplicationComponent modules so that it’s visible everywhere in the graph.

The next step is to modify our binding to construct a map of ViewModel types and their factories:

Notice that previously we were binding the ViewModel s directly to the map, but now we’re binding the ViewModel factories instead into the map.

If you recall from DetailViewModelFactory , we have a constructor that takes two arguments: SavedStateRegistryOwner and a nullable Bundle . We need to supplement our ViewModelFactory to take these two extra arguments in addition to a map of ViewModel types and their factories.

As we can see in the code snippet above, our injected map now holds ViewModelAssistedFactory<out ViewModel>> ies instead of Provider so that we can now call create() and pass the SavedStateHandle to the factory viewModelMap[modelClass]?.create(handle) .

Dagger needs to know how to inject the two new arguments ( SavedStateRegistryOwner and a nullable Bundle ) so let’s set up the bindings for them as well:

The snippet above is intentionally written in Java due to the added static provide method. I prefer writing Dagger modules in Java instead of having a nested @Module annotated companion object.

The only thing left to do now is to inject ViewModelFactory in the Activity and get the ViewModel :

Finally, we inject ViewModelFactory to the Activity and pass its instance to viewModels extension function to get our ViewModel instance with all of its dependencies injected by Dagger via the assisted factory we created.

We now have a full working set up of SavedState, ViewModel and Dagger working together.

I’ve included a sample project, which demonstrates everything discussed in this article on Github.

If you liked this article, please remember to clap and follow me here on Medium and on Twitter for more articles in the future.

References:

Special thanks to Jake Wharton for pointing me to AssistedInject.