# LazyChildItems in Decompose: Lazy Navigation for Dynamic Child Lists

> Explore LazyChildItems in Decompose for efficient lazy navigation of dynamic child lists. Perfect for lists and galleries ensuring components load only when needed. Optimize your UI performance now.

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

---

**LazyChildItems** is a navigation model in the Decompose library that combines an observable `Value<ChildItems<C, T>>` with an `ItemsNavigator<C>` to create child components only when first accessed, making it ideal for lists, galleries, and dynamic UIs.

The `arkivanov/decompose` library provides powerful routing capabilities for Kotlin Multiplatform. When building screens with collections of child components—such as image galleries or data dashboards—you need a navigation pattern that balances state observation with memory efficiency. **LazyChildItems** solves this by deferring component instantiation until the moment a child is actually needed, while still preserving full lifecycle and state restoration capabilities.

## What Is LazyChildItems?

At its core, **LazyChildItems** is an abstract class defined in [`/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main//decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/LazyChildItems.kt) that serves as both a `Value` holder and a navigation controller. It emits the current list of child items through `Value<ChildItems<C, T>>` while implementing `ItemsNavigator<C>` for navigation operations.

The defining characteristic is the required `operator fun get(configuration: C): T` method. This operator is invoked when you access a child by its configuration, and it must return the component instance—creating it lazily if it doesn't already exist. According to the source in [`LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/LazyChildItems.kt) lines 11-25, this is the only abstract member you must implement.

## How LazyChildItems Works Under the Hood

The concrete implementation, **DefaultLazyChildItems**, resides in [`/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultLazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main//decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/DefaultLazyChildItems.kt). This class delegates navigation commands to an internal `ItemsController` while managing the lazy instantiation logic.

When you call `get(configuration)`, the implementation inserts the requested configuration with the lifecycle state `CREATED` before fetching the instance, as seen in lines 6-28 of [`DefaultLazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/DefaultLazyChildItems.kt). This ensures that the child component is properly registered in the navigation state before being returned, maintaining lifecycle consistency without eager instantiation of the entire list.

## When to Use LazyChildItems

Choose **LazyChildItems** when your UI presents collections of child components where eager creation would waste memory or slow startup. Specific scenarios include:

- **Large or dynamic lists** (e.g., a gallery of thumbnails): Only the visible or needed components are instantiated, reducing memory and start-up cost.
- **Complex lifecycle handling**: The model tracks each child's lifecycle state (`CREATED`, `STARTED`, `RESUMED`) individually via `ChildItems` and `ItemsNavigator`, allowing you to start, stop, or destroy children independently.
- **State-preserving navigation**: When using the `childItems` factory with a `NavStateSaver` (lines 30-45 of [`ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildItemsFactory.kt)), the configuration list persists across process recreation while maintaining lazy instantiation.
- **Avoiding duplicate configurations**: The model enforces uniqueness of configurations, preventing subtle bugs caused by duplicate keys (lines 14-18 of [`ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildItemsFactory.kt)).
- **Main-thread safety**: All API calls (including `get`) are intended for the main thread, guaranteeing thread-safe lifecycle transitions (lines 22-23 of [`LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/LazyChildItems.kt)).

## Creating LazyChildItems with the childItems Factory

Rather than implementing the abstract class directly, you typically create instances using the **`childItems`** factory function defined in [`/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main//decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt). This function constructs a `DefaultLazyChildItems` instance with full navigation and state-saving support.

The factory signature requires:
- A `NavigationSource<ItemsNavigation.Event<C>>` to handle navigation commands
- An `initialItems` lambda providing the starting `Items<C>` configuration
- A `childFactory` lambda that creates the component instance given a configuration and `ComponentContext`

Optionally, you can provide a `NavStateSaver<Items<C>>` to enable automatic state preservation across configuration changes, as shown in lines 30-45 of [`ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildItemsFactory.kt).

## Critical Usage Rules for LazyChildItems

The Decompose source code enforces several strict usage patterns to prevent lifecycle corruption and race conditions:

1. **Never call `get` recursively during navigation**: Invoking `get(configuration)` while a navigation operation is in progress throws an `IllegalStateException` (documented in [`LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/LazyChildItems.kt) lines 16-20).
2. **Do not remove a child during its own instantiation**: Attempting to destroy a component while `get` is creating it triggers the same exception.
3. **Maintain configuration uniqueness**: Duplicate configuration values in the items list cause a runtime exception (enforced in [`ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildItemsFactory.kt) lines 14-18).
4. **Restrict calls to the main thread**: Both the `childItems` factory and `LazyChildItems.get` must be called on the main thread to ensure safe lifecycle transitions (noted in [`LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/LazyChildItems.kt) lines 22-23 and [`ChildItemsFactory.kt`](https://github.com/arkivanov/decompose/blob/main/ChildItemsFactory.kt) lines 19-20).

## Practical Implementation Examples

The Decompose repository provides concrete implementations demonstrating both custom inheritance and factory-based creation.

### Custom LazyChildItems Implementation

For scenarios where you need full control over component storage, inherit from `LazyChildItems` directly. The `SimpleLazyChildItems` sample in the repository shows a minimal implementation where all children are pre-instantiated but exposed through the lazy API:

```kotlin
@OptIn(ExperimentalDecomposeApi::class)
class SimpleLazyChildItems<C : Any, T : Any>(
    private val items: Map<C, T>,
) : LazyChildItems<C, T>(), ItemsNavigator<C> by ItemsNavigation() {

    private val _value = MutableValue(
        ChildItems(
            items = items.keys.toList(),
            activeItems = items.mapValues { (_, instance) -> instance to ActiveLifecycleState.CREATED }
        )
    )

    override val value: ChildItems<C, T> by _value::value
    override fun subscribe(observer: (ChildItems<C, T>) -> Unit) = _value.subscribe(observer)
    override fun get(configuration: C): T = items.getValue(configuration)
}

```

*Source*: [SimpleLazyChildItems.kt](/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/SimpleLazyChildItems.kt)

This pattern is useful when integrating with existing component caches or when testing navigation logic without full lazy instantiation.

### Gallery Component with Navigation

For production UIs, use the `childItems` factory to create a navigable list. The sample gallery implementation demonstrates declaring the type and creating the instance:

```kotlin
interface GalleryComponent {
    @OptIn(ExperimentalDecomposeApi::class)
    val items: LazyChildItems<Image, ThumbnailComponent>
    fun onCloseClicked()
}

```

*Source*: [GalleryComponent.kt](/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/sharedtransitions/gallery/GalleryComponent.kt)

The factory invocation typically occurs in the component's implementation:

```kotlin
@OptIn(ExperimentalDecomposeApi::class)
fun ComponentContext.galleryChildItems(
    source: NavigationSource<ItemsNavigation.Event<Image>>,
    initialImages: List<Image>
): LazyChildItems<Image, ThumbnailComponent> =
    childItems(
        source = source,
        initialItems = { Items(items = initialImages) },
        childFactory = { image, ctx -> ThumbnailComponent(image, ctx) }
    )

```

*Source*: [ChildItemsFactory.kt](/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/items/ChildItemsFactory.kt)

This approach defers `ThumbnailComponent` creation until the UI actually requests a specific image, significantly improving startup performance for large galleries.

## Summary

**LazyChildItems** provides a memory-efficient navigation pattern for Decompose applications that need to manage lists of child components. Key takeaways include:

- **LazyChildItems** is an abstract class combining `Value<ChildItems<C, T>>` with `ItemsNavigator<C>` to create children on first access via the `get(configuration)` operator.
- Use **DefaultLazyChildItems** (via the `childItems` factory) for production apps requiring navigation, state saving, and automatic lifecycle management.
- Ideal for **large lists, galleries, and dynamic dashboards** where eager instantiation would waste memory or slow startup.
- Always call `get` on the **main thread**, ensure **configuration uniqueness**, and never invoke `get` recursively during navigation operations.

## Frequently Asked Questions

### What is the difference between LazyChildItems and standard Child Items navigation?

**LazyChildItems** specifically defers component instantiation until the `get(configuration)` method is called, whereas standard child navigation might create components eagerly when the configuration list changes. This lazy behavior is essential for performance when dealing with large datasets or expensive component initialization, as it ensures only actively viewed children consume resources.

### Can I use LazyChildItems without the childItems factory?

Yes, you can inherit directly from the abstract `LazyChildItems` class and implement the `get` operator yourself, as demonstrated in the `SimpleLazyChildItems` sample. However, for most production use cases, the `childItems` factory is recommended because it automatically handles navigation events, state serialization via `NavStateSaver`, and lifecycle state management through `DefaultLazyChildItems`.

### How does LazyChildItems handle state preservation across process death?

When creating `LazyChildItems` via the `childItems` factory, you can optionally provide a `NavStateSaver<Items<C>>` parameter. This serializer persists the list of configurations across process recreation, while the lazy instantiation guarantee ensures that components are only recreated (via the `childFactory` lambda) when they are first accessed after restoration, not during the initial state recovery phase.

### Is LazyChildItems thread-safe?

**LazyChildItems** is designed for single-threaded use on the main thread. The source code in [`LazyChildItems.kt`](https://github.com/arkivanov/decompose/blob/main/LazyChildItems.kt) explicitly requires that all API calls, including the `get` operator and factory initialization, occur on the main thread to ensure safe lifecycle transitions. While the internal implementation protects against concurrent modification during navigation, you must not call `get` from background threads or recursively during navigation operations.