# How ComponentContext Manages Lifecycle Events in Decompose: A Deep Dive into the Source Code

> Explore how ComponentContext manages Decompose lifecycle events with MergedLifecycle, synchronizing parent and child states for seamless component management. Dive into the source code.

- Repository: [Arkadii Ivanov/decompose](https://github.com/arkivanov/decompose)
- Tags: deep-dive
- Published: 2026-02-25

---

**ComponentContext acts as the central lifecycle gateway in Decompose, using a `MergedLifecycle` mechanism to synchronize parent and child component states, ensuring children never exceed the lifecycle state of their parents.**

In the `arkivanov/decompose` library, every component receives a `ComponentContext` through its constructor, making it the single source of truth for lifecycle management, state preservation, and back-press handling. Understanding how this context propagates lifecycle events—particularly to child components—is essential for building robust, memory-leak-free navigation stacks.

## Understanding the ComponentContext Interface

### The Core Abstraction

At the heart of the system lies `GenericComponentContext`, defined in [`GenericComponentContext.kt`](https://github.com/arkivanov/decompose/blob/main/GenericComponentContext.kt). This interface bundles four essential cross-cutting concerns that every Decompose component needs:

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

```

The `ComponentContext` interface itself simply extends this generic version:

```kotlin
interface ComponentContext : GenericComponentContext<ComponentContext>

```

Source: [ComponentContext.kt](https://github.com/arkivanov/decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt)

### The Four Essential Owners

Each owner in `GenericComponentContext` handles a specific concern:

- **LifecycleOwner**: Provides access to the Essenty `Lifecycle` for observing create, start, resume, pause, stop, and destroy events.
- **StateKeeperOwner**: Manages `StateKeeper` for Android-like saved state handling across configuration changes.
- **InstanceKeeperOwner**: Retains instances across navigation changes via `InstanceKeeper`.
- **BackHandlerOwner**: Handles back-press events through `BackHandler`.

## DefaultComponentContext: The Production Implementation

The concrete implementation used in production and tests is `DefaultComponentContext`, located in [`DefaultComponentContext.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultComponentContext.kt):

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

```

When auxiliary objects are not supplied, the constructor automatically creates:
- A `StateKeeperDispatcher` for state preservation
- An `InstanceKeeperDispatcher` attached to the lifecycle
- A `BackDispatcher` for handling back presses

Source: [DefaultComponentContext.kt](https://github.com/arkivanov/decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt)

## How Lifecycle Events Propagate to Child Components

### Creating Child Contexts with ComponentContextFactory

When a parent component creates a child (such as a stack destination), it uses the `componentContextFactory` to generate a new context. This factory is defined in `DefaultComponentContext` as:

```kotlin
override val componentContextFactory: ComponentContextFactory<ComponentContext> =
    ComponentContextFactory(::DefaultComponentContext)

```

The parent invokes it with child-specific instances:

```kotlin
componentContext.componentContextFactory(
    lifecycle = mergedLifecycle,
    stateKeeper = childStateKeeper,
    instanceKeeper = childInstanceKeeper,
    backHandler = childBackHandler,
)

```

Source: [ComponentContextFactory.kt](https://github.com/arkivanov/decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextFactory.kt)

### The MergedLifecycle Mechanism

The core of lifecycle management in Decompose is `MergedLifecycle`, located in [`MergedLifecycle.kt`](https://github.com/arkivanov/decompose/blob/main/MergedLifecycle.kt). This class ensures that a child component never exceeds the lifecycle state of its parent.

When creating a child, the `ChildController` (in [`ChildController.kt`](https://github.com/arkivanov/decompose/blob/main/ChildController.kt)) constructs a `MergedLifecycle` from two sources:

```kotlin
val mergedLifecycle = MergedLifecycle(
    componentContext.lifecycle,
    componentLifecycleRegistry,
)

```

Here, `componentLifecycleRegistry` is a fresh `LifecycleRegistry` for the child component.

### State Synchronization Logic

`MergedLifecycle` subscribes to both lifecycles simultaneously and maintains the child at the **minimum** state of the two. The implementation works as follows:

1. **Initial State Capture**: The constructor captures the initial states of both parent and child lifecycles.

2. **Dual Observation**: It registers `CallbacksImpl` observers on both lifecycles, tracking `state1` (parent) and `state2` (child).

3. **Minimum State Enforcement**: Whenever either lifecycle changes, it invokes `moveTo(minOf(state1, state2))`, ensuring the merged lifecycle never exceeds the more restrictive of the two.

4. **Cleanup**: When the merged lifecycle reaches `DESTROYED`, both observers are automatically unsubscribed to prevent memory leaks.

This guarantees that if the parent pauses or stops, the child immediately follows, even if the child's own registry indicates it should be resumed.

Source: [MergedLifecycle.kt](https://github.com/arkivanov/decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/lifecycle/MergedLifecycle.kt)

## Practical Implementation Examples

### Subscribing to Lifecycle Events in a Component

Components typically delegate to `ComponentContext` to gain direct access to lifecycle methods:

```kotlin
class MyComponent(
    private val componentContext: ComponentContext,
) : ComponentContext by componentContext {

    init {
        // React to lifecycle events
        componentContext.lifecycle.doOnDestroy {
            // cleanup resources
        }
    }
}

```

This pattern is common throughout the Decompose samples, such as in [`DefaultTabsComponent.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultTabsComponent.kt).

Source: [DefaultTabsComponent.kt](https://github.com/arkivanov/decompose/blob/master/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/tabs/DefaultTabsComponent.kt)

### Creating a Child Stack Item

When implementing navigation with `ChildStack`, the factory method uses the context factory to propagate lifecycles correctly:

```kotlin
val childItem = childFactory(
    config,
    componentContext.componentContextFactory(
        lifecycle = MergedLifecycle(componentContext.lifecycle, childRegistry),
        stateKeeper = StateKeeperDispatcher(),
        instanceKeeper = InstanceKeeperDispatcher(),
        backHandler = BackDispatcher()
    )
)

```

The `childRegistry` is a fresh `LifecycleRegistry` for the child, automatically synchronized with the parent's lifecycle via `MergedLifecycle`.

Source: [ChildController.kt](https://github.com/arkivanov/decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildController.kt)

## Summary

- **ComponentContext** serves as the lifecycle gateway for every Decompose component, bundling `LifecycleOwner`, `StateKeeperOwner`, `InstanceKeeperOwner`, and `BackHandlerOwner`.
- **DefaultComponentContext** provides the production implementation, automatically creating dispatchers for state, instances, and back-press handling when not explicitly provided.
- **MergedLifecycle** is the critical mechanism ensuring child components never exceed their parent's lifecycle state by maintaining both at the minimum of the two states.
- **ComponentContextFactory** enables type-safe creation of child contexts, used internally by `ChildController` when navigating between stack destinations.
- Lifecycle observers are typically managed through delegation (`by componentContext`), allowing components to react to `doOnDestroy` and other lifecycle callbacks directly.

## Frequently Asked Questions

### What is the relationship between ComponentContext and LifecycleOwner?

`ComponentContext` extends `GenericComponentContext`, which itself extends `LifecycleOwner` from the Essenty library. This means every component context exposes a `lifecycle` property that components can observe for create, start, resume, pause, stop, and destroy events. The lifecycle is typically provided by `DefaultComponentContext` or a custom implementation.

### How does MergedLifecycle handle parent destruction?

When the parent lifecycle reaches the `DESTROYED` state, `MergedLifecycle` immediately moves the merged state to `DESTROYED` regardless of the child's state. The class automatically unsubscribes its observers from both parent and child lifecycles upon destruction to prevent memory leaks. This ensures that child components are properly cleaned up when their parent is removed from the navigation stack.

### Can I create a ComponentContext without DefaultComponentContext?

Yes, you can implement the `ComponentContext` interface directly or use the `GenericComponentContext` interface if you need custom behavior. However, `DefaultComponentContext` is the recommended implementation as it correctly wires together `StateKeeperDispatcher`, `InstanceKeeperDispatcher`, and `BackDispatcher`. If you provide custom implementations of these dispatchers, you can pass them directly to the `DefaultComponentContext` constructor.

### Where does the actual lifecycle state management happen in Decompose?

The actual state synchronization logic resides in [`MergedLifecycle.kt`](https://github.com/arkivanov/decompose/blob/main/MergedLifecycle.kt), specifically within the `CallbacksImpl` inner class that observes both parent and child lifecycles. The `moveTo` function updates the internal `LifecycleRegistry` to reflect the minimum state of the two sources. This file is imported and used by [`ChildController.kt`](https://github.com/arkivanov/decompose/blob/main/ChildController.kt) when creating child components in navigation stacks.