StateKeeper vs InstanceKeeper in Decompose: When to Use Each for State Management
StateKeeper persists serializable data across process death and configuration changes, while InstanceKeeper retains non-serializable object instances only across component recreation, not process death.
When building multiplatform applications with Decompose (arkivanov/decompose), managing state across Android configuration changes and process recreation requires understanding two distinct mechanisms. While both StateKeeper and InstanceKeeper help survive component destruction, they serve fundamentally different purposes—one handles serialization for process survival, while the other retains object instances for configuration changes.
Core Differences Between StateKeeper and InstanceKeeper
| Aspect | StateKeeper | InstanceKeeper |
|---|---|---|
| Purpose | Persists serializable data for process restoration | Retains arbitrary object instances across recreation |
| Survives Process Death | Yes | No |
| Data Type | Serializable data only | Any object (often non-serializable) |
| Implementation | StateKeeperDispatcher with SerializableContainer |
InstanceKeeperDispatcher with key-value registry |
| Lifecycle | Bound to component lifecycle, saves on destroy | Bound to component lifecycle, destroys when component truly disposed |
Understanding StateKeeper for Serializable State Persistence
StateKeeper is designed for state preservation—saving UI state, navigation stacks, or user input that must survive process death. It wraps a StateKeeperDispatcher that stores a SerializableContainer, ensuring data can be written to and restored from the Android saved state mechanism or equivalent platform implementations.
In ChildStateKeeper.kt, the library creates a child StateKeeperDispatcher and registers it with the parent's registry, enabling hierarchical state management where each component maintains isolated state keys.
class CounterComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {
private val stateKeeper = stateKeeper.child(key = "counter", lifecycle = lifecycle)
private var count: Int = 0
set(value) {
field = value
stateKeeper.register(key = "value", serializer = Int.serializer()) { value }
}
init {
count = stateKeeper.consume(key = "value", serializer = Int.serializer())?.value ?: 0
}
fun increment() {
count++
}
}
Understanding InstanceKeeper for Object Retention
InstanceKeeper serves a different purpose: instance retention. It holds references to arbitrary objects (often non-serializable) that should survive configuration changes but don't need to persist through process death. This mimics AndroidX ViewModel behavior, keeping heavy objects, coroutine scopes, or service handles in memory during recreation.
The implementation in ChildInstanceKeeper.kt creates a child InstanceKeeperDispatcher that maintains a registry of Instance objects. When the component is finally destroyed (not just recreated), the dispatcher triggers onDestroy() on retained instances.
class CounterComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {
private val logic: CounterLogic = instanceKeeper.getOrCreate(key = "logic") {
CounterLogic()
}
fun startCounting() = logic.start()
fun stopCounting() = logic.stop()
}
class CounterLogic : InstanceKeeper.Instance {
private var job: Job? = null
fun start() {
job = CoroutineScope(Dispatchers.Default).launch {
while (isActive) {
delay(250)
}
}
}
fun stop() {
job?.cancel()
}
override fun onDestroy() {
stop()
}
}
Key Implementation Files
Both mechanisms are implemented in the Decompose source code with clear separation of concerns:
-
ChildStateKeeper.kt: Creates child
StateKeeperDispatcherinstances and registers them with the parent's state registry. Located atdecompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/ChildStateKeeper.kt. -
ChildInstanceKeeper.kt: Creates child
InstanceKeeperDispatcherinstances and manages their lifecycle attachment. Located atdecompose/src/commonMain/kotlin/com/arkivanov/decompose/instancekeeper/ChildInstanceKeeper.kt. -
ComponentContext.kt: Defines the interface aggregating
LifecycleOwner,StateKeeperOwner, andInstanceKeeperOwner, providing access to both mechanisms. Located atdecompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt.
Summary
- StateKeeper persists serializable data across process death using
StateKeeperDispatcherandSerializableContainer, ideal for UI state and navigation stacks. - InstanceKeeper retains non-serializable object instances across configuration changes using
InstanceKeeperDispatcher, suitable for heavy objects and coroutine scopes. - Both are scoped per component via
ComponentContext, withChildStateKeeper.ktandChildInstanceKeeper.kthandling hierarchical delegation. - StateKeeper survives process death; InstanceKeeper does not.
Frequently Asked Questions
Can I use StateKeeper and InstanceKeeper together in the same component?
Yes. In fact, most production components use both mechanisms simultaneously. Use StateKeeper for serializable UI state that must survive process death, and InstanceKeeper for non-serializable heavy objects or coroutine scopes that only need to survive configuration changes.
Does InstanceKeeper survive process death like StateKeeper?
No. InstanceKeeper only retains objects in memory during component recreation, such as Android configuration changes. When the process is killed and restored, InstanceKeeper-held objects are recreated from scratch, whereas StateKeeper-restored data is available immediately.
How do I choose between StateKeeper and InstanceKeeper for my data?
Choose StateKeeper if your data is serializable (primitives, data classes with @Serializable, lists) and represents UI state the user expects to persist after backgrounding the app. Choose InstanceKeeper if your data is non-serializable (database connections, ViewModels, coroutine scopes) or expensive to recreate, and you only need it to survive configuration changes, not process death.
Where are StateKeeper and InstanceKeeper implemented in the Decompose source code?
The hierarchical delegation logic resides in ChildStateKeeper.kt and ChildInstanceKeeper.kt within the decompose/src/commonMain/kotlin/com/arkivanov/decompose/ directory tree. The underlying dispatchers are defined in the Essenty library, which Decompose depends on for both StateKeeperDispatcher and InstanceKeeperDispatcher implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →