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:

Summary

  • StateKeeper persists serializable data across process death using StateKeeperDispatcher and SerializableContainer, 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, with ChildStateKeeper.kt and ChildInstanceKeeper.kt handling 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →