Claude
Skills
Sign in
Back

decompose

Included with Lifetime
$97 forever

Decompose navigation and components - use for KMP component architecture, navigation, lifecycle, and state management

Design

What this skill does


# Decompose for Kotlin Multiplatform

Component-based architecture with lifecycle management and navigation for KMP.

## Setup

### libs.versions.toml

```toml
[versions]
decompose = "3.5.0"
essenty = "2.5.0"

[libraries]
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }
essenty-lifecycle = { module = "com.arkivanov.essenty:lifecycle", version.ref = "essenty" }
```

### build.gradle.kts

```kotlin
commonMain.dependencies {
    implementation(libs.decompose)
    implementation(libs.decompose.compose)
    implementation(libs.essenty.lifecycle)
    implementation(libs.kotlinx.serialization.json)
}
```

## Core Concepts

### Component

Business logic container with lifecycle. UI-agnostic.

```kotlin
// Interface (public API)
interface HomeComponent {
    val state: Value<HomeState>
    fun onItemClick(item: HomeItem)
    fun onRefresh()
}

// Implementation
class DefaultHomeComponent(
    componentContext: ComponentContext,
    private val repository: HomeRepository,
    private val onNavigateToDetails: (itemId: String) -> Unit
) : HomeComponent, ComponentContext by componentContext {

    private val _state = MutableValue<HomeState>(HomeState.Loading)
    override val state: Value<HomeState> = _state

    private val scope = componentScope()

    init {
        loadData()
    }

    private fun loadData() {
        scope.launch {
            _state.value = HomeState.Loading
            repository.getItems()
                .onSuccess { items ->
                    _state.value = HomeState.Success(items)
                }
                .onError { message, _ ->
                    _state.value = HomeState.Error(message)
                }
        }
    }

    override fun onItemClick(item: HomeItem) {
        onNavigateToDetails(item.id)
    }

    override fun onRefresh() {
        loadData()
    }
}

sealed class HomeState {
    data object Loading : HomeState()
    data class Success(val items: List<HomeItem>) : HomeState()
    data class Error(val message: String) : HomeState()
}
```

### ComponentContext

Provides lifecycle, state preservation, and child management.

```kotlin
class MyComponent(
    componentContext: ComponentContext
) : ComponentContext by componentContext {

    // Access lifecycle
    init {
        lifecycle.subscribe(
            onCreate = { println("Created") },
            onStart = { println("Started") },
            onResume = { println("Resumed") },
            onPause = { println("Paused") },
            onStop = { println("Stopped") },
            onDestroy = { println("Destroyed") }
        )
    }

    // Retain instances across config changes (Android)
    private val viewModel = instanceKeeper.getOrCreate { MyViewModel() }

    // Preserve state during process death
    private var counter: Int by savedState("counter", 0)

    // Create coroutine scope tied to lifecycle
    private val scope = componentScope()
}

// Helper extension — place wherever your project keeps shared lifecycle utils.
// If you have a `core/` or `shared/core/` module, put it there so every feature can
// reuse it. If not, paste this snippet into your feature's impl module
// (e.g. `feature/<name>/impl/.../component/ComponentScope.kt`) — it has no Decompose-
// specific deps beyond `essenty`, so duplication is cheap.
import com.arkivanov.essenty.lifecycle.doOnDestroy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel

fun ComponentContext.componentScope(): CoroutineScope {
    val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
    lifecycle.doOnDestroy { scope.cancel() }
    return scope
}
```

## Navigation

### Child Stack (Primary Navigation)

Stack-based navigation like a navigation controller.

```kotlin
interface RootComponent {
    val childStack: Value<ChildStack<Config, Child>>

    sealed class Child {
        data class Home(val component: HomeComponent) : Child()
        data class Details(val component: DetailsComponent) : Child()
        data class Settings(val component: SettingsComponent) : Child()
    }

    @Serializable
    sealed class Config {
        @Serializable data object Home : Config()
        @Serializable data class Details(val itemId: String) : Config()
        @Serializable data object Settings : Config()
    }
}

class DefaultRootComponent(
    componentContext: ComponentContext,
    private val homeComponentFactory: HomeComponent.Factory,
    private val detailsComponentFactory: DetailsComponent.Factory,
    private val settingsComponentFactory: SettingsComponent.Factory
) : RootComponent, ComponentContext by componentContext {

    private val navigation = StackNavigation<RootComponent.Config>()

    override val childStack: Value<ChildStack<RootComponent.Config, RootComponent.Child>> =
        childStack(
            source = navigation,
            serializer = RootComponent.Config.serializer(),
            initialConfiguration = RootComponent.Config.Home,
            handleBackButton = true,  // Auto handle back
            childFactory = ::createChild
        )

    private fun createChild(
        config: RootComponent.Config,
        context: ComponentContext
    ): RootComponent.Child = when (config) {
        RootComponent.Config.Home -> RootComponent.Child.Home(
            homeComponentFactory.create(
                componentContext = context,
                onNavigateToDetails = { itemId ->
                    navigation.push(RootComponent.Config.Details(itemId))
                }
            )
        )
        is RootComponent.Config.Details -> RootComponent.Child.Details(
            detailsComponentFactory.create(
                componentContext = context,
                itemId = config.itemId,
                onBack = { navigation.pop() }
            )
        )
        RootComponent.Config.Settings -> RootComponent.Child.Settings(
            settingsComponentFactory.create(context)
        )
    }

    // Public navigation methods
    fun navigateToSettings() {
        navigation.push(RootComponent.Config.Settings)
    }
}
```

### Child Slot (Modals/Dialogs)

Single optional active child.

```kotlin
interface HomeComponent {
    val dialogSlot: Value<ChildSlot<DialogConfig, DialogChild>>
    fun showConfirmDialog(itemId: String)
    fun dismissDialog()
}

@Serializable
sealed class DialogConfig {
    @Serializable data class Confirm(val itemId: String) : DialogConfig()
    @Serializable data class Edit(val item: HomeItem) : DialogConfig()
}

sealed class DialogChild {
    data class Confirm(val component: ConfirmDialogComponent) : DialogChild()
    data class Edit(val component: EditDialogComponent) : DialogChild()
}

class DefaultHomeComponent(
    componentContext: ComponentContext
) : HomeComponent, ComponentContext by componentContext {

    private val dialogNavigation = SlotNavigation<DialogConfig>()

    override val dialogSlot: Value<ChildSlot<DialogConfig, DialogChild>> =
        childSlot(
            source = dialogNavigation,
            serializer = DialogConfig.serializer(),
            childFactory = ::createDialog
        )

    private fun createDialog(
        config: DialogConfig,
        context: ComponentContext
    ): DialogChild = when (config) {
        is DialogConfig.Confirm -> DialogChild.Confirm(
            ConfirmDialogComponent(
                context = context,
                itemId = config.itemId,
                onConfirm = { deleteItem(config.itemId); dismissDialog() },
                onDismiss = ::dismissDialog
            )
        )
        is DialogConfig.Edit -> DialogChild.Edit(
            EditDialogComponent(context, config.item)
        )
    }

    override fun showConfirmDialog(itemId: String) {
        dialogNavigation.activate(DialogConfig.Confirm(itemId))
    }

    over
Files: 2
Size: 32.5 KB
Complexity: 34/100
Category: Design

Related in Design