agentsclimarketplace

Compose multiplatform navigation

Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/ui/compose-multiplatform-navigation

Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.

Install
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill compose-multiplatform-navigation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Navigation in Compose Multiplatform with Voyager or Decompose, including deep links, back-handling, and nested graphs. Use when designing multi-screen KMP apps.

SKILL.md

4.9 KB, as published. Nobody here has run it

Compose Multiplatform Navigation

Instructions

androidx.navigation:navigation-compose is Android-only. For CMP, use Voyager (screen-model centric, Compose-first) or Decompose (lifecycle + state-holder centric, better for complex nav / multi-window). Pick one per app.

1. Voyager — minimal, idiomatic Compose

// commonMain/build.gradle.kts
commonMain.dependencies {
    implementation(libs.voyager.navigator)
    implementation(libs.voyager.screenmodel)
    implementation(libs.voyager.transitions)
}
data object HomeScreen : Screen {
    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        Column {
            Text("Home")
            Button(onClick = { navigator.push(DetailScreen(id = 42)) }) { Text("Open 42") }
        }
    }
}

data class DetailScreen(val id: Long) : Screen {
    @Composable
    override fun Content() {
        val screenModel = rememberScreenModel { DetailScreenModel(id) }
        val state by screenModel.state.collectAsState()
        Text("Detail $id: ${state.title}")
    }
}

@Composable
fun App() = Navigator(HomeScreen) { nav ->
    SlideTransition(nav)
}

Back handling works automatically via BackHandler on Android and via the Compose iOS runtime's system back. For deep links, resolve the URL at your entry point and navigator.replaceAll(...) the target screen:

fun handleDeepLink(url: String, nav: Navigator) {
    val uri = Url(url)
    when (uri.pathSegments.firstOrNull()) {
        "item" -> nav.replaceAll(listOf(HomeScreen, DetailScreen(uri.pathSegments[1].toLong())))
    }
}

2. Decompose — component tree, great for tablet / master-detail

sealed interface RootComponent {
    val stack: Value<ChildStack<*, Child>>
    fun onBackClicked()
    sealed interface Child {
        class Home(val component: HomeComponent) : Child
        class Detail(val component: DetailComponent) : Child
    }
}

class DefaultRootComponent(componentContext: ComponentContext) :
    RootComponent, ComponentContext by componentContext {

    private val nav = StackNavigation<Config>()

    override val stack: Value<ChildStack<*, RootComponent.Child>> = childStack(
        source = nav,
        serializer = Config.serializer(),
        initialConfiguration = Config.Home,
        handleBackButton = true,
    ) { config, ctx ->
        when (config) {
            Config.Home -> RootComponent.Child.Home(DefaultHomeComponent(ctx, ::openDetail))
            is Config.Detail -> RootComponent.Child.Detail(DefaultDetailComponent(ctx, config.id))
        }
    }

    private fun openDetail(id: Long) = nav.pushNew(Config.Detail(id))
    override fun onBackClicked() = nav.pop()

    @Serializable
    private sealed interface Config {
        @Serializable data object Home : Config
        @Serializable data class Detail(val id: Long) : Config
    }
}
@Composable
fun RootUi(component: RootComponent) {
    Children(stack = component.stack, animation = stackAnimation(slide())) {
        when (val child = it.instance) {
            is RootComponent.Child.Home -> HomeUi(child.component)
            is RootComponent.Child.Detail -> DetailUi(child.component)
        }
    }
}

Decompose integrates with Android SavedStateHandle, iOS lifecycle (via LifecycleRegistry wired in the iOS entry point), and produces a serializable back stack — robust against process death.

3. Deep links

Centralize URL parsing in commonMain. Emit typed intents that navigation code consumes:

sealed interface DeepLink {
    data object Home : DeepLink
    data class Item(val id: Long) : DeepLink
}

fun parseDeepLink(raw: String): DeepLink? = runCatching {
    val url = Url(raw)
    when (url.pathSegments.firstOrNull()) {
        "item" -> DeepLink.Item(url.pathSegments[1].toLong())
        else -> DeepLink.Home
    }
}.getOrNull()

Android consumes via Activity.onNewIntent; iOS via SceneDelegate/@main App.

4. Back press & gesture-back

  • Android: the navigator library registers a BackHandler; don't register your own globally.
  • iOS: Compose MP 1.7+ honours interactive pop gestures automatically when a Navigator/Decompose stack is non-empty.
  • Desktop: wire your own keyboard shortcut (e.g. Esc) using onPreviewKeyEvent.

Checklist

  • Exactly one navigation library in use — no mixing navigation-compose into a CMP app.
  • Screen models (Voyager) or components (Decompose) live in commonMain.
  • Deep-link parsing returns typed results, no raw strings leaking into UI.
  • Back stack survives process death (Decompose serializer set; Voyager rememberScreen with stable keys).
  • iOS back-swipe and Android hardware back both work without custom code.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.