santimattius/performance-compose-skills/compose-navigation-nav3
Claude Code and agent skill by santimattius/performance-compose-skills
- community
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-nav3SKILL.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, useorg.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.
| Phase | What Runs | Restart Cost | Trigger | |
|---|---|---|---|---|
| Composition | Composable functions, state reads | HIGH (whole scope) | State read in composable body | |
| Layout | Measure + place | MEDIUM (subtree) | State read in measure lambda | |
| Drawing | Canvas commands, graphicsLayer | LOW (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
() -> TintoModifier.layout/offset { }lambda - Draw/transform? → pass
() -> TintoModifier.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
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 withprofileableenabled or a benchmark build type.
| Tool | Use For | When | |
|---|---|---|---|
| Baseline Profiles | AOT-compile critical paths (startup, scroll) | Ship in release; regenerate per release | |
| Compose Compiler Reports | Detect unstable params, restartable/skippable status | Every PR; fail CI on new unstable types | |
| Layout Inspector (recomposition counts) | See which composables recompose and why | When debugging excess recomposition | |
| Composition Tracing | Frame-level composition timing in Android Studio | When Layout Inspector is not enough | |
| Macrobenchmark | Measure startup, frame timing, jank in release | Per-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 set | Status | Notes | |
|---|---|---|---|
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.
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.
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.
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.
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.
6. Minimum Requirements
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
| Feature | Nav2 | Nav3 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
| Nav2 | Nav3 | Notes | |
|---|---|---|---|
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 SavedStateHandle | Arguments as NavKey data class fields | entry<UserKey> { key -> key.userId } | |
navOptions { launchSingleTop = true } | Manual: remove existing + add | No built-in equivalents |
Pitfalls
| Pitfall | Fix | Phase Cost | |
|---|---|---|---|
Missing @Serializable on NavKey | Add @Serializable + kotlinx.serialization plugin | Process death = navigation state lost | |
No rememberViewModelStoreNavEntryDecorator | Add decorator to entryDecorator param | ViewModel memory leak on every pop | |
Using mutableStateListOf as backstack | Use rememberNavBackStack | No process-death save/restore | |
| Passing a ViewModel to an entry's content composable | Pass state + lambdas only | Couples nav entry to ViewModel, breaks Screen/Content split | |
| Empty backstack passed to NavDisplay | Always initialize with at least one key | IllegalArgumentException at runtime | |
| Navigating to undeclared entry key | Ensure all keys have a matching entry<Key> | IllegalStateException at runtime | |
Using BottomSheetSceneStrategy without copying implementation | Copy from Nav3 recipes — it is not in androidx.navigation3 | Compile error |
Code Examples
Minimal Nav3 Setup
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
- SKILL.md in the repositoryCheckingskills/compose-navigation-nav3/SKILL.md
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
[](https://verified-skill.com/skills/santimattius/performance-compose-skills/compose-navigation-nav3)