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.