Decompose GenericComponentContext Architecture: Core Abstraction and Extensions

GenericComponentContext is the unified interface in Decompose that aggregates lifecycle, state preservation, instance retention, and back handling capabilities while providing a factory for creating hierarchical child contexts.

The GenericComponentContext interface forms the architectural foundation of the Decompose navigation library by Arkadii Ivanov. This generic abstraction unifies four essential Essenty owner interfaces and enables the recursive creation of component contexts, making it possible to build complex navigation hierarchies with scoped state management.

The GenericComponentContext Interface

At the center of Decompose's architecture lies the GenericComponentContext<T> interface defined in decompose/src/commonMain/kotlin/com/arkivanov/decompose/GenericComponentContext.kt. This interface extends five critical contracts that provide the full lifecycle and state management toolkit for any component:

interface GenericComponentContext<out T : Any> :
    LifecycleOwner,
    StateKeeperOwner,
    InstanceKeeperOwner,
    BackHandlerOwner,
    ComponentContextFactoryOwner<T>

By aggregating these Essenty owners, GenericComponentContext enables components to react to lifecycle events, persist UI state across process death, retain long-living objects, and handle back-press navigation. The generic type parameter T allows the interface to remain covariant while supporting recursive context creation through the factory pattern.

Factory Pattern and Context Creation

The ability to spawn new contexts without manual dependency wiring comes from two supporting interfaces. First, ComponentContextFactoryOwner<T> (located in decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextFactoryOwner.kt) exposes the factory:

val componentContextFactory: ComponentContextFactory<T>

Second, ComponentContextFactory<T> (in decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextFactory.kt) defines the construction contract as a functional interface:

fun interface ComponentContextFactory<out T : Any> {
    operator fun invoke(
        lifecycle: Lifecycle,
        stateKeeper: StateKeeper,
        instanceKeeper: InstanceKeeper,
        backHandler: BackHandler,
    ): T
}

Navigation models like childStack leverage this factory to instantiate fresh, detached contexts programmatically. The operator fun invoke allows syntactically clean usage: parentContext.componentContextFactory(...).

DefaultComponentContext Implementation

The concrete implementation DefaultComponentContext (in decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt) wires together the default Essenty dispatchers:

class DefaultComponentContext(
    override val lifecycle: Lifecycle,
    stateKeeper: StateKeeper? = null,
    instanceKeeper: InstanceKeeper? = null,
    backHandler: BackHandler? = null,
) : ComponentContext

This class automatically provides StateKeeperDispatcher, InstanceKeeperDispatcher (attached to the lifecycle), and BackDispatcher when null values are passed. The factory implementation simply references the constructor: ComponentContextFactory(::DefaultComponentContext), enabling recursive creation of identical context types.

Hierarchical Child Contexts

The childContext extension function in decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt enables the creation of scoped child contexts that maintain proper lifecycle boundaries:

fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(
    key: String,
    lifecycle: Lifecycle? = null,
    backHandlerPriority: Int = 0,
): Ctx

This function performs three critical operations:

  • Merges lifecycles using MergedLifecycle when a child lifecycle is provided, or inherits the parent lifecycle when null
  • Forks state and instance keepers via child(key, lifecycle) calls on the parent's keepers, creating isolated scopes
  • Creates prioritized back handlers allowing children to intercept navigation events before parents

The resulting context is constructed through the parent's componentContextFactory, ensuring type consistency across the hierarchy.

Jetpack Integration Architecture

For Android-specific requirements, JetpackComponentContext (in jetpack-component-context/src/commonMain/kotlin/com/arkivanov/decompose/jetpackcomponentcontext/JetpackComponentContext.kt) extends the generic architecture:

interface JetpackComponentContext :
    GenericComponentContext<JetpackComponentContext>,
    androidx.lifecycle.LifecycleOwner,
    SavedStateRegistryOwner,
    ViewModelStoreOwner,
    HasDefaultViewModelProviderFactory

This interface bridges Decompose's generic lifecycle with Android Jetpack's Lifecycle, SavedStateRegistry, and ViewModelStore. The conversion helper enables interoperability:

fun <T : GenericComponentContext<T>> T.asJetpackComponentContext(): JetpackComponentContext

The implementation uses a nested Lifecycle class that satisfies both Essenty's Lifecycle and Jetpack's LifecycleOwner contracts simultaneously, allowing a single object to serve both ecosystems.

Practical Usage Examples

Creating a Root Context

import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.essenty.lifecycle.LifecycleRegistry

val rootLifecycle = LifecycleRegistry().apply {
    create()
    start()
    resume()
}
val rootContext = DefaultComponentContext(lifecycle = rootLifecycle)

Spawning a Child with Scoped State

import com.arkivanov.decompose.childContext

val childContext = rootContext.childContext(
    key = "featureA",
    lifecycle = null,  // Inherits parent lifecycle
    backHandlerPriority = 1  // Handles back before siblings
)

Converting for Android ViewModel Usage

// In an Android-specific module
val jetpackContext = rootContext.asJetpackComponentContext()
val viewModel = jetpackContext.viewModel<MyViewModel>()

Implementing a Custom Context Type

interface AnalyticsContext : 
    GenericComponentContext<AnalyticsContext>,
    AnalyticsProvider {

    class Impl(
        override val lifecycle: Lifecycle,
        override val stateKeeper: StateKeeper,
        override val instanceKeeper: InstanceKeeper,
        override val backHandler: BackHandler,
    ) : AnalyticsContext {
        override val componentContextFactory = 
            ComponentContextFactory(::Impl)
        override val analytics: Analytics = AnalyticsImpl()
    }
}

Summary

  • GenericComponentContext aggregates LifecycleOwner, StateKeeperOwner, InstanceKeeperOwner, BackHandlerOwner, and ComponentContextFactoryOwner into a single generic contract.
  • Factory interfaces enable navigation models to create detached contexts without manual Essenty object construction.
  • DefaultComponentContext provides the standard concrete implementation wiring Essenty dispatchers together.
  • childContext extensions create hierarchical scopes with merged lifecycles, forked keepers, and prioritized back handling.
  • JetpackComponentContext extends the generic architecture to support Android-specific lifecycle, saved state, and ViewModel requirements.

Frequently Asked Questions

What is the difference between GenericComponentContext and ComponentContext?

ComponentContext is a type alias for GenericComponentContext<ComponentContext>, serving as the default concrete type used by most Decompose components. GenericComponentContext<T> is the generic base interface that supports custom context types while maintaining the core Essenty owner contracts and factory capabilities.

How does childContext maintain proper scoping?

The childContext extension in ComponentContextExt.kt creates isolated scopes by calling stateKeeper.child(key) and instanceKeeper.child(key, lifecycle), which return forked keepers tied to the child's lifecycle. This ensures state and instances are properly garbage collected when the child lifecycle is destroyed, preventing memory leaks in deeply nested navigation stacks.

Can I use GenericComponentContext with Android Jetpack ViewModels?

Yes. By calling asJetpackComponentContext() on any GenericComponentContext, you receive a JetpackComponentContext that implements ViewModelStoreOwner and HasDefaultViewModelProviderFactory. This allows direct usage of by viewModels() or by activityViewModels() delegates within Decompose components while maintaining the library's multiplatform architecture.

What is the purpose of the ComponentContextFactory functional interface?

ComponentContextFactory abstracts the construction logic for creating new contexts, allowing navigation machinery to instantiate child contexts without knowing the specific implementation class. The factory receives raw Essenty primitives (Lifecycle, StateKeeper, etc.) and returns a properly configured context of type T, enabling type-safe hierarchical composition across custom context 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 →