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.

Figure 1: SavedState components

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.