santimattius/performance-compose-skills/compose-navigation-nav3

Claude Code and agent skill by santimattius/performance-compose-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Jetpack Compose Navigation 3 (Nav3 1.0.0 stable, Android-only as of Nav3 1.0.0): NavKey, NavDisplay, NavBackStack, SceneStrategy, ViewModel decorators, process-death safety, and Nav2→Nav3 migration. CMP Nav3 available via org.jetbrains.androidx.navigation3 1.0.0-alpha05 from CMP 1.10+. Trigger: when implementing navigation in Compose with Nav3, migrating from Nav2, using NavKey, NavDisplay, SceneStrategy, bottom sheet/dialog navigation, wiring ViewModels to navigation entries, or choosing CMP navigation alternatives (compose-router, Decompose, Voyager).

Install

npx vskill@latest install santimattius/performance-compose-skills/compose-navigation-nav3

SKILL.md

VALIDATED 2026-05 · Nav3 1.0.0 (stable, Android) · CMP Nav3 1.0.0-alpha05 (CMP 1.10+)
⚠️ `androidx.navigation3` is Android-only. For Compose Multiplatform, use org.jetbrains.androidx.navigation3:navigation3-ui (CMP 1.10+ / alpha) or see CMP alternatives.

Performance First

Every Compose frame runs three phases. Cost rises left-to-right; restart scope shrinks left-to-right.

PhaseWhat RunsRestart CostTrigger
CompositionComposable functions, state readsHIGH (whole scope)State read in composable body
LayoutMeasure + placeMEDIUM (subtree)State read in measure lambda
DrawingCanvas commands, graphicsLayerLOW (single node)State read in draw/graphicsLayer lambda
Defer State Reads (decision tree)

Do you need the value during composition?

  • YES → read as T (Composition phase) — accept full restart cost
  • NO → can you defer to layout/draw?
  • Layout-only? → pass () -> T into Modifier.layout/offset { } lambda
  • Draw/transform? → pass () -> T into Modifier.graphicsLayer { } / drawBehind { } lambda

Rule: prefer Modifier.offset { lambda } over Modifier.offset(state.value.dp). Lambda variant defers the read to the layout phase, skipping recomposition entirely.

ViewModelStoreDecorator — Preventing ViewModel Re-creation on Navigation
kotlin
// ✅ With rememberViewModelStoreNavEntryDecorator — ViewModels survive back-stack pops
@Composable
fun NavigationRoot() {
val navBackStack = rememberNavBackStack(HomeKey)
val vmStoreDecorator = rememberViewModelStoreNavEntryDecorator()
NavDisplay(
backStack = navBackStack,
entryDecorator = vmStoreDecorator, // ← prevents ViewModel re-creation per entry
entryProvider = entryProvider { ... }
)
}
// ❌ Without decorator — every navigation pop destroys and recreates the ViewModel
// This causes: unnecessary composition cost + memory churn + state loss
NavDisplay(
backStack = navBackStack,
entryProvider = entryProvider { ... } // no decorator = no ViewModel scope management
)

Each nav entry without rememberViewModelStoreNavEntryDecorator creates a fresh ViewModel on every push and leaks the old one on pop (never cleared). Compose cost: full recomposition of the entry as state initializes from scratch.


Performance Toolchain

WARNING: NEVER profile in debug builds. Debug disables R8, inlining, and Strong Skipping — numbers are meaningless. Always profile a release build with profileable enabled or a benchmark build type.
ToolUse ForWhen
Baseline ProfilesAOT-compile critical paths (startup, scroll)Ship in release; regenerate per release
Compose Compiler ReportsDetect unstable params, restartable/skippable statusEvery PR; fail CI on new unstable types
Layout Inspector (recomposition counts)See which composables recompose and whyWhen debugging excess recomposition
Composition TracingFrame-level composition timing in Android StudioWhen Layout Inspector is not enough
MacrobenchmarkMeasure startup, frame timing, jank in releasePer-release regression gate

When to Use

Use this skill when:

  • Implementing navigation in a new Compose app
  • Migrating from Nav2 (NavController, NavHost) to Nav3 (NavDisplay, NavBackStack)
  • Setting up multi-pane navigation (list-detail, tablet layouts)
  • Wiring ViewModels to navigation entries safely
  • Adding dialogs or bottom sheets as navigation destinations
  • Handling process death and back-stack state restoration

When to Use This vs compose-architecture

compose-architecture covers ViewModel scoping, collectAsStateWithLifecycle, and the Screen/Content split. compose-navigation-nav3 covers how entries are structured, how the back stack is managed, and how ViewModel lifecycle is tied to navigation entries via decorators. Use both when connecting ViewModels to specific nav destinations.


CMP Applicability

Canonical CMP rules: `../_shared/cmp-platform.md`
Source setStatusNotes
commonMain⚠️CMP Nav3 alpha: org.jetbrains.androidx.navigation3:navigation3-ui 1.0.0-alpha05 (CMP 1.10+)
androidMain✅Full skill content applies; use androidx.navigation3:navigation3-compose (stable 1.0.0)
iosMain⚠️CMP Nav3 alpha only; or use Decompose/Voyager for stable production nav
desktopMain⚠️CMP Nav3 alpha only; same alternatives apply
wasmJsMain⚠️CMP Nav3 alpha only; same alternatives apply

Status legend: ✅ fully supported · ⚠️ partial / version-gated · ❌ Android-only.

If using in CMP: Use org.jetbrains.androidx.navigation3:navigation3-ui 1.0.0-alpha05 (requires CMP 1.10+). On non-JVM targets, NavKey serialization uses kotlinx.serialization `SavedStateConfiguration` — not reflection. The API surface (NavDisplay, decorators, rememberNavBackStack) is identical to the Android stable artifact. For stable production nav on non-Android targets, see references/cmp-alternatives.md.

Audit pattern CMP-NAV-COMMONMAIN: import androidx.navigation3 in commonMain without the JetBrains CMP artifact → route here for guidance on migrating to org.jetbrains.androidx.navigation3.


Critical Patterns

1. NavKey — Every Route MUST Implement This Interface

NavKey is a marker interface. Every destination MUST implement it AND be @Serializable so rememberNavBackStack can persist and restore the back stack across process death.

kotlin
// ✅ Object destination (no arguments)
@Serializable
data object HomeKey : NavKey
// ✅ Data class destination (with arguments)
@Serializable
data class UserProfileKey(val userId: String) : NavKey
// ❌ Missing @Serializable — rememberNavBackStack cannot restore state after process death
data class BrokenKey(val id: String) : NavKey
2. rememberNavBackStack — REQUIRED for Process-Death Save/Restore

Use rememberNavBackStack to create the back stack. It automatically serializes and restores navigation state. Alternatives do NOT survive process death.

kotlin
// ✅ Survives process death automatically
val navBackStack = rememberNavBackStack(HomeKey)
// ❌ Does NOT survive process death — state lost on system kill
val manualBackStack = retain { mutableStateListOf<Any>() }
val saveableBackStack = rememberSaveable { mutableStateListOf<Any>() }
3. rememberViewModelStoreNavEntryDecorator — REQUIRED, Not Optional

Without this decorator, ViewModels associated with navigation entries are NEVER cleared when the entry is popped from the back stack. This is a guaranteed memory leak in any app using ViewModels with Nav3.

kotlin
@Composable
fun NavigationRoot() {
val navBackStack = rememberNavBackStack(HomeKey)
// REQUIRED — must be created before NavDisplay
val vmStoreDecorator = rememberViewModelStoreNavEntryDecorator()
NavDisplay(
backStack = navBackStack,
entryDecorator = vmStoreDecorator,
entryProvider = entryProvider {
entry<HomeKey> { HomeScreen() }
}
)
}
4. rememberSaveableStateHolderNavEntryDecorator — Per-Entry UI State

Preserves rememberSaveable state per entry as users navigate. Without it, UI state (scroll position, text input, etc.) is reset every time the entry is re-entered.

kotlin
val saveableDecorator = rememberSaveableStateHolderNavEntryDecorator()
val vmStoreDecorator = rememberViewModelStoreNavEntryDecorator()
NavDisplay(
backStack = navBackStack,
// Chain decorators — order matters: saveable wraps vm store
entryDecorator = saveableDecorator then vmStoreDecorator,
entryProvider = entryProvider { ... }
)
5. BottomSheetSceneStrategy — NOT in Core Artifact

There is NO built-in BottomSheetSceneStrategy in the Nav3 core library. You MUST copy the implementation from the Nav3 recipes/samples or write it yourself. The core only ships SinglePaneSceneStrategy, ListDetailSceneStrategy, and DialogSceneStrategy.

kotlin
// ✅ What IS in core:
sceneStrategy = SinglePaneSceneStrategy() // default
sceneStrategy = rememberListDetailSceneStrategy() // list-detail
val dialogStrategy = remember { DialogSceneStrategy<NavKey>() }
// ❌ What is NOT in core — must copy from recipes:
// BottomSheetSceneStrategy — copy from Nav3 sample/recipes repo
// Custom BottomSheet scene strategy usage (after copying implementation):
val bottomSheetStrategy = remember { BottomSheetSceneStrategy<NavKey>() }
NavDisplay(
backStack = navBackStack,
sceneStrategy = rememberListDetailSceneStrategy() then bottomSheetStrategy,
entryProvider = entryProvider {
entry<BottomSheetKey>(
metadata = BottomSheetSceneStrategy.bottomSheet()
) {
MyBottomSheetContent(onDismiss = navBackStack::removeLastOrNull)
}
}
)
6. Minimum Requirements
compileSdk = 36
minSdk = 23

Confirm in build.gradle.kts before enabling Nav3. Using a lower compileSdk will cause compilation errors on Nav3 APIs.

7. Nav2 Features NOT Supported in Nav3
FeatureNav2Nav3 Status
Deep links✅ Supported❌ Not supported in 1.0.0
Nested nav graphs > 1 level✅ Supported❌ Not supported
Shared destinations across backstacks✅ Possible❌ Not supported
NavController.navigate(route: String)✅ Supported❌ Replaced by back-stack mutation
SavedStateHandle argument injection✅ Supported❌ Use NavKey data class fields instead
8. Nav2 → Nav3 API Mapping Table
Nav2Nav3Notes
rememberNavController()rememberNavBackStack(initial)The backstack IS the controller
NavHost { }NavDisplay(entryProvider = { })NavDisplay observes the backstack
composable<Key> { }entry<Key> { }Inside entryProvider { }
dialog<Key> { }entry<Key>(metadata = DialogSceneStrategy.dialog())Needs DialogSceneStrategy registered
navController.navigate(key)navBackStack.add(key)Direct list mutation
navController.popBackStack()navBackStack.removeLastOrNull()Direct list mutation
Arguments via SavedStateHandleArguments as NavKey data class fieldsentry<UserKey> { key -> key.userId }
navOptions { launchSingleTop = true }Manual: remove existing + addNo built-in equivalents

Pitfalls

PitfallFixPhase Cost
Missing @Serializable on NavKeyAdd @Serializable + kotlinx.serialization pluginProcess death = navigation state lost
No rememberViewModelStoreNavEntryDecoratorAdd decorator to entryDecorator paramViewModel memory leak on every pop
Using mutableStateListOf as backstackUse rememberNavBackStackNo process-death save/restore
Passing a ViewModel to an entry's content composablePass state + lambdas onlyCouples nav entry to ViewModel, breaks Screen/Content split
Empty backstack passed to NavDisplayAlways initialize with at least one keyIllegalArgumentException at runtime
Navigating to undeclared entry keyEnsure all keys have a matching entry<Key>IllegalStateException at runtime
Using BottomSheetSceneStrategy without copying implementationCopy from Nav3 recipes — it is not in androidx.navigation3Compile error

Code Examples

Minimal Nav3 Setup
kotlin
@Serializable data object HomeKey : NavKey
@Serializable data object DetailKey : NavKey
@Composable
fun NavigationRoot() {
val navBackStack = rememberNavBackStack(HomeKey)
val vmStoreDecorator = rememberViewModelStoreNavEntryDecorator()

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/santimattius/performance-compose-skills/compose-navigation-nav3/badge)](https://verified-skill.com/skills/santimattius/performance-compose-skills/compose-navigation-nav3)

Something wrong with this skill or its page? Tell us.