Saving state

Saving and restoring state

It is possible that an application is killed while the user has navigated to a particular Scene. When the user returns to the application it is expected that the application restores to the state it was left in. This means that it is necessary to be able to restore Scenes from a serialized state.

For 'static' Scenes that take no arguments (like our HelloWorldScene above) this usually is no problem. Scenes that do take arguments or have other state they wish to preserve need to implement the SavableScene interface. This introduces a saveInstanceState function, allowing the Scene to persist its state to a serializable format. When the Scene needs to be restored, this serialized state will then be provided.

 1 class ShowItemScene(
 2     private val itemId: Long
 3 ) : Scene<ShowItemContainer>, StateSaveable {
 4 
 5     override fun saveInstanceState(): SceneState {
 6         return sceneState {
 7             it["item_id"] = itemId
 8         }
 9     }
10     
11     companion object {
12     
13         fun create(state: SceneState): ShowItemScene {
14             return ShowItemScene(itemId = state["item_id"])
15         }
16     }
17 }

When a Scene implements the SavableScene interface, Navigators can call the saveInstanceState function to retrieve the serializable state.

Container state

Container state (or view state) saving and restoring is a very important topic for mobile applications. Whenever a user has entered text or scrolled a list to a particular position and navigates away from the screen to later return again, it is expected that the entered text or the scroll position is still there.

Next to saving their own state, Scenes are also responsible for saving and restoring the Container states. Since multiple Containers can be attached to and detached from the Scene, their state needs to be saved and restored between the Container instances as well. This can easily be done by saving the Container state in the detach method, and restoring it in the attach method.

Finally, when saving the Scene state, the most recent container state needs to be persisted as well. The BasicScene class provides a base implementation that handles all this.

In Acorn

By default, a Scene does not support state saving, nor does it save or restore view hierarchy state such as user input or scroll positions. From a user's perspective however, it is important that you do save your Scene's state: not only can an Android application be killed at any time after which it should be properly restored, but a lost scroll position on orientation change can also be very annoying.

View hierarchy state saving

During the lifetime of a Scene it can happen that it receives multiple calls to attach and detach. Often, subsequent calls to attach will have fresh instances of the Container passed to it, losing any view hierarchy state. The Activity being recreated due to a device orientation change is one example of this.

The Scene can save and restore the container state between these subsequent calls to attach if the Container type implements the RestorableContainer interface. This interface provides a saveInstanceState method and a restoreInstanceState method, which the Scene can use to restore the container state:

 1 interface MyContainer: RestorableContainer
 2 
 3 class MyScene : Scene<MyContainer> {
 4 
 5     private var containerState: ContainerState? = null
 6 
 7     override fun attach(v: MyContainer) {
 8         containerState?.let { v.restoreInstanceState(it) }
 9     }
10 
11     override fun detach(v: MyContainer) {
12         containerState = v.saveInstanceState()
13     }
14 }

This Scene will now save the Container's instance state when it gets detached from the Scene. A new Container instance that gets attached to the Scene will receive the previous Container's state and can restore the view hierarchy.

Scene state saving

To have your Scene's state saved to prepare for process deaths, implement the SavableScene interface. This interface provides a saveInstanceState() method that will be called at appropriate times.
When saving a Scene, you generally want to save as little as possible, but just enough to be able to reconstruct it after process death. Think of saving a userId value, but not the entire User instance.

Next to this, you can also choose to save the view hierarchy state with it, if the Container supports it:

 1 interface MyContainer: RestorableContainer
 2 
 3 class MyScene(
 4     private val userId: String
 5 ) : Scene<MyContainer>, SavableScene {
 6 
 7     private var containerState: ContainerState? = null
 8 
 9     override fun attach(v: MyContainer) {
10         containerState?.let { v.restoreInstanceState(it) }
11     }
12 
13     override fun detach(v: MyContainer) {
14         containerState = v.saveInstanceState()
15     }
16 
17     override fun saveInstanceState(): SceneState {
18         return sceneState {
19             it["user_id"] = userId
20             it["container_state"] = containerState
21         }
22     }
23 }

This snippet extends the previous snippet by implementing the SavableScene interface and overriding the saveInstanceState() method.

Scene state restoration

After a process death, a Navigator class can ask you to restore your Scene from a saved state (see Navigators).
If your Scene implements SavableScene, you will be passed the SceneState instance that you returned from saveInstanceState(). You can then pull out everything you need to be able to restore the Scene.

If we again expand on the previous example, we can add restoration support by implementing a create method in the Scene's companion object as follows:

 1 interface MyContainer: RestorableContainer
 2 
 3 class MyScene(
 4     private val userId: String,
 5     savedState: SceneState? = null
 6 ) : Scene<MyContainer>, SavableScene {
 7 
 8     private var containerState: ContainerState? = savedState?.get("container_state")
 9 
10     override fun attach(v: MyContainer) {
11         containerState?.let { v.restoreInstanceState(it) }
12     }
13 
14     override fun detach(v: MyContainer) {
15         containerState = v.saveInstanceState()
16     }
17 
18     override fun saveInstanceState(): SceneState {
19         return sceneState {
20             it["user_id"] = userId
21             it["container_state"] = containerState
22         }
23     }
24 
25     companion object {
26 
27         fun create(savedState: SceneState) : MyScene {
28             return MyScene(
29                 savedState["user_id"],
30                 savedState
31             )
32         }
33     }
34 }

Now, we have restored our userId from the saved state, as well as any view state that was saved with it.