# How to Implement Custom Navigation Using ChildrenFactory in Decompose

> Learn to implement custom navigation in Decompose using ChildrenFactory. Master state persistence, lifecycle transitions, and back-button handling with immutable NavState snapshots and pure-function transformations.

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

---

**The `children` factory extension provides the low-level API for building custom navigation models in Decompose, orchestrating state persistence, lifecycle transitions, and back-button handling through immutable `NavState` snapshots and pure-function navigation transformations.**

For navigation patterns that extend beyond standard stacks or slots, the **arkivanov/decompose** library offers the **ChildrenFactory** API. This extension allows you to implement custom navigation using ChildrenFactory in Decompose by defining immutable navigation states and controlling every aspect of child component lifecycles. Unlike high-level helpers such as `childStack` or `childSlot`, the Children Factory gives you direct control over which children are created, started, resumed, or destroyed at any given moment.

## Core Components of the Children Factory API

Three interfaces form the foundation of custom navigation in Decompose. Understanding their roles is essential before implementing your own navigation model.

**`NavState<C>`** is an immutable snapshot of your entire navigation hierarchy. Defined in [`decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt`](https://github.com/arkivanov/decompose/blob/main/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt), it exposes a single property: `children: List<ChildNavState<C>>`. This list must contain one entry for every possible child configuration, with each element annotated with a lifecycle status.

**`ChildNavState<C>`** represents a single child’s configuration alongside its lifecycle state. Located in [`decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt`](https://github.com/arkivanov/decompose/blob/main/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt), this interface tracks the `configuration: C` and a `status` that can be `CREATED`, `STARTED`, `RESUMED`, or `DESTROYED`. Only one child typically holds the `RESUMED` status at a time, indicating it is currently visible and interactive.

**`ChildrenFactory`** (the `children(...)` extension function) orchestrates the entire navigation system. Found in [`decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt`](https://github.com/arkivanov/decompose/blob/main/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt), it receives a `NavigationSource<E>` (commonly `SimpleNavigation`), manages state serialization, handles back-button callbacks, and instantiates child components through a factory lambda.

## Step-by-Step Implementation Guide

### Step 1: Define Your Navigation State

Create a serializable data class that implements `NavState<C>`. This class represents the complete state of your navigation graph at any moment. In [`DefaultCustomNavigationComponent.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultCustomNavigationComponent.kt), the sample implements a carousel/pager navigation model:

```kotlin
@Serializable
private data class Config(val imageResourceId: ImageResourceId)

@Serializable
private data class NavigationState(
    val configurations: List<Config>,
    val index: Int,
    val mode: Mode,
) : NavState<Config> {

    override val children: List<SimpleChildNavState<Config>> by lazy {
        configurations.mapIndexed { i, cfg ->
            SimpleChildNavState(
                configuration = cfg,
                status = if (i == index) ChildNavState.Status.RESUMED
                         else ChildNavState.Status.CREATED
            )
        }
    }
}

```

The `children` property must return a list where every possible configuration is represented exactly once. The status determines which component receives lifecycle callbacks—only the `RESUMED` child is actively visible, while others remain `CREATED` (preserved in memory but stopped).

### Step 2: Create a Navigation Source

Instantiate a `SimpleNavigation` source that emits state transformation functions. This pattern, defined in [`decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt`](https://github.com/arkivanov/decompose/blob/main/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt), maintains a queue of pending navigation actions expressed as pure functions:

```kotlin
private val navigation = SimpleNavigation<(NavigationState) -> NavigationState>()

```

`SimpleNavigation` acts as a `NavigationSource<(S) -> S>`, allowing you to push immutable state updates without exposing mutable state to your UI layer.

### Step 3: Configure the Children Factory

Invoke the `children()` extension from within your `ComponentContext` to wire everything together. This function requires several key parameters to implement custom navigation using ChildrenFactory in Decompose:

```kotlin
private val _children: Value<Children<Config, KittenComponent>> = children(
    source = navigation,
    stateSerializer = NavigationState.serializer(),
    key = "carousel",
    initialState = {
        NavigationState(
            configurations = ImageResourceId.entries.map(::Config),
            index = 0,
            mode = Mode.CAROUSEL
        )
    },
    navTransformer = { state, transform -> transform(state) },
    stateMapper = { state, children ->
        Children(
            items = children.map { it as Child.Created },
            index = state.index,
            mode = state.mode
        )
    },
    backTransformer = {
        it.takeIf { it.index > 0 }?.let { state ->
            { state.copy(index = state.index - 1) }
        }
    },
    childFactory = { config, ctx ->
        DefaultKittenComponent(componentContext = ctx, imageResourceId = config.imageResourceId)
    }
)

```

**Key parameters explained:**

- **`navTransformer`**: Receives the current `NavState` and a navigation event (the transformer function from your source), returning the next state.
- **`stateMapper`**: Converts the raw `NavState` and list of created children into a UI-specific model wrapped in `Value<S>`.
- **`backTransformer`**: Optionally returns a function that produces a new state when the back button is pressed. Returning `null` disables back handling for the current state.
- **`childFactory`**: Creates actual component instances for each configuration, receiving the configuration and a child-specific `ComponentContext`.

### Step 4: Trigger Navigation Events

Navigation actions are expressed as pure functions that receive the current state and return a transformed copy. This ensures immutability and makes testing straightforward:

```kotlin
fun onForwardClicked() {
    navigation.navigate { state ->
        state.copy(index = (state.index + 1) % state.configurations.size)
    }
}

```

All calls to `navigation.navigate` are queued and applied sequentially, ensuring thread safety when called from the main thread (strongly recommended by the API documentation).

## Internal Architecture and Lifecycle Management

When you invoke `children()`, Decompose instantiates a `ChildrenNavigator` internally (located in [`decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt`](https://github.com/arkivanov/decompose/blob/main/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt)). This class performs several critical operations:

- **Diff calculation**: Compares the previous list of `ChildNavState` with the new one to determine which children need creation, destruction, or lifecycle transitions.
- **Lifecycle orchestration**: Automatically moves children between `CREATED`, `STARTED`, and `RESUMED` states based on your `NavState` updates.
- **State persistence**: Uses the provided `stateSerializer` with the component's `StateKeeper` to automatically save and restore navigation state across configuration changes.
- **Back handling**: Registers a `BackCallback` that is enabled only when `backTransformer` returns a non-null function for the current state, ensuring the back button respects your custom navigation logic.

## Summary

- **ChildrenFactory** is the low-level API in `arkivanov/decompose` for implementing navigation patterns beyond stacks and slots.
- **NavState** and **ChildNavState** define immutable snapshots of your navigation hierarchy and individual child statuses.
- **SimpleNavigation** provides a thread-safe event source using pure function transformations.
- The `children()` function requires `navTransformer`, `stateMapper`, `backTransformer`, and `childFactory` to bridge state changes with component lifecycles.
- Source files in `decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/` contain the core interfaces, while [`DefaultCustomNavigationComponent.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultCustomNavigationComponent.kt) provides a working carousel example.

## Frequently Asked Questions

### What is the difference between ChildrenFactory and childStack?

**`childStack`** is a high-level convenience API built on top of ChildrenFactory specifically for back-stack navigation with push/pop semantics. **ChildrenFactory** exposes the underlying primitive that powers `childStack`, allowing you to define arbitrary navigation shapes such as carousels, wizard steps, or multi-pane layouts where multiple children might be visible simultaneously.

### How does state preservation work with custom navigation?

Decompose automatically persists your navigation state using the `stateSerializer` parameter passed to the `children()` function. When the system kills and recreates your process, the `NavState` is deserialized and passed as the `initialState`, restoring your exact navigation position including the lifecycle status of every child component.

### Can I use ChildrenFactory for multi-pane or master-detail layouts?

Yes. Unlike `childStack`, which assumes a single active child, ChildrenFactory supports multiple `RESUMED` children simultaneously. By setting several children to `ChildNavState.Status.RESUMED` in your `NavState` implementation, you can keep multiple panes active—such as a master list and detail view side-by-side on tablets.

### How do I disable the back button for specific navigation states?

Return `null` from the `backTransformer` lambda for states where back navigation should be disabled. The `ChildrenNavigator` only registers an active `BackCallback` when `backTransformer` produces a non-null transformation function, effectively disabling system back gestures when your UI is in a state that should not be popped (such as the first step of a wizard).