mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
Added a comprehensive collection of Compose development guidelines, best practices, and skill definitions in the .codex directory. Co-authored-by: Junie <junie@jetbrains.com>
92 lines
5.0 KiB
Markdown
92 lines
5.0 KiB
Markdown
# Navigation
|
|
|
|
Shared navigation concepts for Nav 2 and Nav 3. Load first, then see version-specific references.
|
|
|
|
References:
|
|
- [Nav 3 official docs](https://developer.android.com/guide/navigation/navigation-3)
|
|
- [Nav 2 official docs](https://developer.android.com/guide/navigation/get-started)
|
|
- [Kotlin CMP Nav 3 docs](https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html)
|
|
|
|
## Nav 2 vs Nav 3 Decision Guide
|
|
|
|
| Criterion | Nav 3 (NavDisplay) | Nav 2 (NavHost / NavController) |
|
|
|---|---|---|
|
|
| Back stack ownership | You own it (`SnapshotStateList`) | Library owns it (`NavController`) |
|
|
| Navigation model | List manipulation — `add()`, `removeLastOrNull()` | Imperative — `navigate()`, `popBackStack()` |
|
|
| MVI alignment | Natural — back stack is state you mutate | Requires bridging — controller calls in effect handlers |
|
|
| Deep link parsing | You parse URIs, construct back stack manually | Built-in `NavDeepLink` parsing |
|
|
| Scenes / adaptive layouts | First-class: dialog, bottom sheet, list-detail | Manual: separate composable overlays |
|
|
| CMP support | Full (Android, iOS, Desktop, Web) | Android-only (JetBrains forks exist but differ) |
|
|
| Maturity | Newer — verify artifact stability for production | Stable, battle-tested |
|
|
| Fragment interop | None | Full Fragment/Activity integration |
|
|
|
|
**When to use Nav 3:**
|
|
- New Compose projects following MVI architecture
|
|
- Compose Multiplatform projects targeting multiple platforms
|
|
- Projects wanting direct back stack control as state
|
|
- Projects needing adaptive layout scenes (list-detail, dialog, bottom sheet)
|
|
|
|
**When to use Nav 2:**
|
|
- Existing codebases already built on `NavHost`/`NavController`
|
|
- Projects requiring built-in deep link parsing via `NavDeepLink`
|
|
- Hybrid Compose + Fragment apps where Nav 2 provides Fragment integration
|
|
- Teams that prefer the declarative `NavGraph` DSL
|
|
|
|
## Navigation in MVI
|
|
|
|
The architectural rule: **ViewModels emit semantic effects; the route layer handles navigation.** This rule applies identically to both Nav 2 and Nav 3.
|
|
|
|
```kotlin
|
|
sealed interface ItemEffect {
|
|
data object NavigateBack : ItemEffect
|
|
data class OpenDetails(val id: String) : ItemEffect
|
|
}
|
|
|
|
// Nav 3 route layer — manipulates back stack
|
|
CollectEffect(viewModel.effect) { effect ->
|
|
when (effect) {
|
|
is ItemEffect.NavigateBack -> backStack.removeLastOrNull()
|
|
is ItemEffect.OpenDetails -> backStack.add(Details(effect.id))
|
|
}
|
|
}
|
|
|
|
// Nav 2 route layer — calls NavController
|
|
CollectEffect(viewModel.effect) { effect ->
|
|
when (effect) {
|
|
is ItemEffect.NavigateBack -> navController.navigateUp()
|
|
is ItemEffect.OpenDetails -> navController.navigate(Detail(effect.id))
|
|
}
|
|
}
|
|
```
|
|
|
|
### Rules
|
|
|
|
- Never call navigation during composition — always in `LaunchedEffect` or event handler callbacks
|
|
- Never pass the back stack (Nav 3) or `NavController` (Nav 2) to the ViewModel or leaf composables
|
|
- ViewModel emits semantic effects (`NavigateBack`, `OpenDetails(id)`)
|
|
- Route/navigation layer translates effects to navigation calls
|
|
- Keep navigation logic at the route boundary, not in screens or leaves
|
|
|
|
## Anti-Patterns
|
|
|
|
| Anti-pattern | Applies to | Why it hurts | Better replacement |
|
|
|---|---|---|---|
|
|
| Navigating during composition | Both | Triggers on every recomposition, causes infinite loops | Navigate in `LaunchedEffect` or event handler callbacks |
|
|
| Passing NavController/back stack to ViewModel | Both | Violates MVI boundary, navigation becomes business logic | ViewModel emits semantic effects; route handles navigation |
|
|
| String-based routes without type safety | Both | No compile-time checking, argument mismatch at runtime | `@Serializable` data classes/objects |
|
|
| Missing `onBack` handler | Nav 3 | System back gesture does nothing | Always provide `onBack = { backStack.removeLastOrNull() }` |
|
|
| Globally-scoped ViewModel for per-screen data | Both | Data leaks across screens, not cleared on pop | Entry-scoped VMs (Nav 3 decorators) or destination-scoped VMs (Nav 2) |
|
|
| Recreating back stacks on tab switch | Both | Loses user navigation history within tabs | Persistent per-tab stacks (Nav 3) or `saveState`/`restoreState` (Nav 2) |
|
|
| Missing entry decorators | Nav 3 | ViewModels leak, saveable state lost | Always include both `rememberSaveableStateHolderNavEntryDecorator` and `rememberViewModelStoreNavEntryDecorator` |
|
|
| Using Nav 2 in new MVI codebases | Nav 3 preferred | Nav 3's user-owned back stack aligns better with MVI state ownership | Prefer Nav 3 `NavDisplay` for new MVI-first projects; Nav 2 remains valid for existing codebases |
|
|
|
|
## Version-Specific References
|
|
|
|
Load the file that matches your task:
|
|
|
|
- **Nav 3 routes, tabs, scenes, deep links, or back stack patterns** → [navigation-3.md](navigation-3.md)
|
|
- **Nav 2 NavHost, tabs, deep links, nested graphs, or animations** → [navigation-2.md](navigation-2.md)
|
|
- **Wiring Hilt or Koin with Nav 3** → [navigation-3-di.md](navigation-3-di.md)
|
|
- **Wiring Hilt or Koin with Nav 2** → [navigation-2-di.md](navigation-2-di.md)
|
|
- **Migrating from Nav 2 to Nav 3** → [navigation-migration.md](navigation-migration.md)
|