Master Kotlin Multiplatform with Decompose — Part 3: Restoring state with InstanceKeeper and StateKeeper
Let’s make the Decompose components retain state across configuration changes and process death.
This article is part of a series on Kotlin Multiplatform with Decompose. If you haven’t read the previous articles, I recommend starting there.
The article covers:
- Creating a component
- Retaining state across configuration changes
- Recovering state after process death
Here is the starting source code.
Creating a component
In previous articles, we created two components: one for holding a list of posts and another for showing detailed post information. In this article, we will create a new component for post creation.
class DefaultCreateComponent(
componentContext: ComponentContext,
) : CreateComponent, ComponentContext by componentContext {
private val state = MutableValue(State()) // 1
override val model: Value<CreateComponent.Model> = state.map {
CreateComponent.Model(
title = it.title,
description = it.description,
author = it.author,
)
}
override fun onNameChanged(value: String) {
state.update { it.copy(title = value) }
}
override fun onDescriptionChanged(value: String) {
state.update { it.copy(description = value) }
}
override fun onAuthorChanged(value: String) {
state.update { it.copy(author = value) }
}
}
- Storing form state within the component.
Run the app
Fill out the form and rotate the phone horizontally.
Our data is not retained during configuration changes due to component recreation. Let's fix it!
Retaining state during configuration changes
The Essenty library, created by the author of the Decompose library, includes InstanceKeeper, similar to ViewModel from Android Jetpack.
Create a new class
private class Handler( // 1
initialState: State
) : InstanceKeeper.Instance { // 2
val state = MutableValue(initialState) // 3
}
- Create a class that retains memory over configuration changes.
- InstanceKeeper.Instance is a destroyable instance with an
onDestroy()
method, similar toonCleared()
in ViewModel. - Hold state within our InstanceKeeper.Instance.
Update the component
class DefaultCreateComponent(
componentContext: ComponentContext,
) : CreateComponent, ComponentContext by componentContext {
// 1
private val handler = instanceKeeper.getOrCreate(INSTANCE_KEY) { Handler(State()) }
override val model: Value<CreateComponent.Model> = handler.state.map {
CreateComponent.Model(
title = it.title,
description = it.description,
author = it.author,
)
}
override fun onNameChanged(value: String) {
handler.state.update { it.copy(title = value) }
}
override fun onDescriptionChanged(value: String) {
handler.state.update { it.copy(description = value) }
}
override fun onAuthorChanged(value: String) {
handler.state.update { it.copy(author = value) }
}
companion object {
private const val INSTANCE_KEY = "instance_key"
}
}
- Retrieve the Handler instance from
instanceKeeper
.
Run the app
Great! Now your class instance is saved over configuration changes.
Recovering state after process death
Although InstanceKeeper retains the class instance in memory during configuration changes, it does not persist through process death. To solve this problem, StateKeeper provided by Decompose serves as a solution, similar to onSaveInstanceState or SavedStateHandle.
Update the component
class DefaultCreateComponent(
componentContext: ComponentContext,
private val onFinished: () -> Unit,
) : CreateComponent, ComponentContext by componentContext {
private val handler = instanceKeeper.getOrCreate(INSTANCE_KEY) {
Handler( // 1
initialState = stateKeeper.consume(STATE_KEY, State.serializer()) ?: State()
)
}
init { // 2
stateKeeper.register(STATE_KEY, State.serializer()) { handler.state.value }
}
...
companion object {
private const val INSTANCE_KEY = "instance_key"
private const val STATE_KEY = "state_key"
}
}
- When our app restores after process death, we retrieve state from
stateKeeper
. - The lambda will be invoked when the process is about to be killed, and the result of the lambda will be saved.
Run app
- Fill out the fields in your application.
- Navigate to the home screen, allowing the app to run in the background.
- Use the adb command to kill the process. Replace
<YOUR APPLICATION ID>
with your actual application ID.adb shell am kill <YOUR APPLICATION ID>
- Reopen the app.
Excellent! The app now restores state after process death.
Conclusion
Congratulations! 🎉 Your components can now be restored from both configuration changes and process death. Check out the source code on GitHub.
What’s next?
There’s room for improvement. Stay tuned for upcoming articles on Decompose and other enhancements.
Other Parts:
- Master Kotlin Multiplication navigation with Decompose — Part 1
- Master Kotlin Multiplatform with Decompose — Part 2: Dependency Injection, Kodein, Koin
- Master Kotlin Multiplatform with Decompose — Part 4: MVI
Connect
Let’s connect on LinkedIn and subscribe for updates!
Thank you!