Android ViewModels: State persistence — SavedState

Introduction
For each app that’s launched on an Android device, the system creates a process to run it. When an app is no longer in the foreground, the system doesn’t kill its process, but it keeps it in a -least recently used- cache. So what happens when the system is low on memory? It frees up memory by removing the least recently used processes in cache. When you then return to an app whose process has been killed, it is restarted in a new process.
For your own apps, there are times when you’ll want your users to be presented with the app in the same state they left it in, even if its process may have been killed by the system. This requires saving the state using the onSaveInstanceState()
lifecycle method, local persistence for more complex objects, and a couple of tricks to make it work. Lyla Fujiwara wrote an amazing article on this topic.
In this blog post, I’ll be going through a new way of achieving the same using a new library called ViewModel SavedState, it is part of Jetpack and is still in alpha, so the API may change before its release.
SavedState components
The SavedState library contains a couple of components that interact with each other in order to save and restore data in cases where the app process is killed.

SavedStateProvider
A component that contributes to the saved state by providing a bundled state it wishes to save before being killed by the system. This state is then later restored and can be consumed.
SavedStateRegistry
A component that manages a list of SavedStateProvider
s that each consume and contribute to the saved state. This registry is bound to its owner’s lifecycle (i.e: an Activity or Fragment), and thus a new instance is created each time the owner is recreated.
Once a registry’s owner is created (for example, after an Activity’s onCreate(savedInstanceState)
method is called), its performRestore(state)
method is called in order to restore any state that may have been saved before the system killed its owner.
Each of the registry’s SavedStateProvider
s is identified by a unique key that’s used to register it.
Once this registration is done, the restored state for a specific key can be consumed through consumeRestoredStateForKey(key)
.
Notice how this method retrieves a saved state and then clears its internal reference to it, meaning that calling it twice with the same key will return null
in the second call.
Another thing to note is that once the registry has restored its saved state, it’s up to the provider to ask for its -restored- data. If it doesn’t, the unconsumed restored data will be saved again to the saved state the next time the registry’s owner is killed by the system.
The last point that hasn’t been discussed is performing state saving. A registered provider will be able to contribute to the saved state before its owner is killed by the system. When this happens, its saveState()
method (which returns a Bundle
) is called. For every registered SavedStateProvider
, contributing to the saved state is done like this.
The performSave(outBundle)
method in its entirety looks like the following.
Performing state saving goes through 2 steps where any unconsumed state is merged with the state from the registry’s providers. This outBundle
saved state is the exact bundle used in an Activity/Fragment’s onSaveInstanceState
.
SavedStateRegistryController
A component that wraps a SavedStateRegistry
and allows to control it via its 2 main methods: performRestore(savedState)
and performSave(outBundle)
. These 2 methods forward their calls to SavedStateRegistry
.
SavedStateRegistryOwner
A component that owns a SavedStateRegistry
. Both ComponentActivity
and Fragment
-in the androidx package- implement this interface by default.
Example: ComponentActivity
In order to better understand how the above components interact with each other, and how (and when) state is saved and restored, I’ve chosen the example of ComponentActivity (a parent class of AppCompatActivity
).
As you may have noticed, ComponentActivity
is a SavedStateRegistryOwner
, which means it must override this interface’s only method getSavedStateRegistry()
. It contains an instance of SavedStateRegistryController
which is initialized as the class starts. In the activity’s onCreate(savedInstanceState)
method, the controller’s performRestore(savedInstanceState)
method is called in order to retrieve any saved state that may have been saved in the bundle. When onSaveInstanceState(outState)
is called (right as the activity moves to the background*), the controller’s performSave(outState)
is invoked in order to save the activity’s state.
*A common mistake is to assume that an activity’s onSaveInstanceState()
is called when the app’s process is about to be killed by the system. This is incorrect, at that point the system is pretty resource constrained.
ViewModels
ViewModels can now contribute to saved state! A couple of new components were created in order to help achieve this.

SavedStateHandle
A component that contains a key/value map of data from the saved state. It allows reading from and writing values to this map, values which will persist after the app’s process is killed.
This handle is passed down to a ViewModel in its constructor. Some of the main methods it offers are:
T get(String key)
: Allows to read the value by its key.MutableLiveData<T> getLiveData(String key)
: Allows to observe a value via LiveData using its key.void set(String key, T value)
: Allows to associate a value with a key. The value must be of a supported type (any one of the supported types aBundle
allows to store).
A SavedStateHandle
also contains an instance of SavedStateProvider
, which contributes to its ViewModel’s owner’s saved state.
AbstractSavedStateVMFactory
A ViewModel factory that implements ViewModelFactory.KeyedFactory
. It basically creates a SavedStateHandle
associated with the requested ViewModel it’s instantiating.
SavedStateVMFactory
A concrete implementation of AbstractSavedStateVMFactory
. It allows to create ViewModels that consume and contribute to a saved state via a SavedStateHandle
.
It has many constructors which accept an instance of a Fragment, Activity or Application in order to create a ViewModel (or AndroidViewModel). The defaultArgs
argument is used to assign a default value to the saved state in case it is null
.
Bringing the pieces together

Once you create a ViewModel instance from -say- your activity using an AbstractSavedStateVMFactory
, it takes as a parameter a reference to this activity (which is a SavedStateRegistryOwner
) and gets access to its SavedStateRegistry
, through it, the factory then consumes the saved state, wraps it in a SavedStateHandle
and passes it down to the ViewModel. The ViewModel now can read and write to the handle.
Once the activity’s onSaveInstanceState(outState)
is called, the registry’s performSave(outState)
is invoked, which consequently calls saveState()
on each of its SavedStateProvider
s, which includes the handle’s provider. Once this is done, outState
contains the saved state.
When the app is restarted, the activity and a new registry are created, the activity’s onCreate(savedInstanceState)
is called, then the registry’s performRestore(savedInstanceState)
is called in order to retrieve the saved state.
When to use SavedState?
When you need to store information regarding the UI’s state so that your user will always come back to the app and find it in the same state they had left it in. Make sure this data is not large, not complex and that it can can be saved in a Bundle
, if it’s not of a simple type it must be Serializable
or Parcelable
. Use SavedState
in order to save the least amount of information you need to reconstruct your UI, when you want to save complex data, consider using a local database for example.
Also keep in mind that the state of every View
object in your layout is automagically bundled and saved for you by the system once your activity is stopped, such as an EditText
's text. This means that if your screen is destroyed by the system then recreated when you return to it, the state of the UI is restored to its previous state.
Code examples
The repo below contains examples of creating SavedState ViewModels in different ways (with and without default arguments for the saved state, using SavedStateVMFactory
vs AbstractSavedStateVMFactory
when the ViewModel has a more complex constructor, creating a ViewModel vs an AndroidViewModel
).
In order to simulate killing the app, you can first make sure its process is running using the command below.
$ adb shell ps -A |grep com.husaynhakeem.savedstateplayground
If the app is running, you should see an output resembling this one.
$ u0_a106 12665 1668 1202872 61028 0 0 S com.husaynhakeem.savedstateplayground
Press on the home button on your device, then run the following command.
$ adb shell am kill com.husaynhakeem.savedstateplayground
This will kill the app’s process. Launch the app once again, you should be able to see that the ViewModel data was persisted (depending on the scenario you ran).