diff --git a/NAV3_RESEARCH.md b/NAV3_RESEARCH.md deleted file mode 100644 index 4a3a7d8..0000000 --- a/NAV3_RESEARCH.md +++ /dev/null @@ -1,841 +0,0 @@ -# Navigation Compose 3: Research Report - -> Compiled 2026-04-03. Sources: Android Developer docs, JetBrains Kotlin docs, nav3-recipes repo, Android Developers Blog, community blog posts, and release notes. - ---- - -## Table of Contents - -1. [Overview](#1-overview) -2. [Core Concepts & API](#2-core-concepts--api) -3. [Best Practices](#3-best-practices) -4. [Migration Guide (Nav 2 → Nav 3)](#4-migration-guide-nav-2--nav-3) -5. [Gotchas, Footguns & Complications](#5-gotchas-footguns--complications) -6. [KMP / Compose Multiplatform Considerations](#6-kmp--compose-multiplatform-considerations) -7. [Appendix: Version & Dependency Reference](#7-appendix-version--dependency-reference) - ---- - -## 1. Overview - -### What is Navigation 3? - -Navigation 3 (Nav3) is a **ground-up rewrite** of Android's Jetpack Navigation, designed specifically for Jetpack Compose. Released as stable **1.0.0 on November 19, 2025**, it replaces the controller-based paradigm of Nav 2 with a **state-based, declarative approach** where the developer owns the back stack directly. - -- **Stable release**: 1.0.1 (Feb 11, 2026) -- **Latest**: 1.1.0-rc01 (Mar 25, 2026) -- **Min SDK**: 23 -- **Compile SDK**: 36+ - -### How It Differs from Nav 2 - -| Aspect | Nav 2 | Nav 3 | -|--------|-------|-------| -| **State ownership** | Library-owned (`NavController`) | Developer-owned (`SnapshotStateList`) | -| **Back stack model** | Hidden internal structure | Transparent mutable list | -| **Navigation API** | `navController.navigate()` | `backStack.add(key)` | -| **Host component** | `NavHost` | `NavDisplay` | -| **Route definition** | `composable { }` in `NavHost` | `entry { }` in `entryProvider` | -| **Layout flexibility** | Single pane only | Multi-pane via Scenes API | -| **Nested graphs** | `navigation { }` | Not needed — flat entry list | -| **Design philosophy** | Opinionated framework | Flexible building blocks | -| **Compose alignment** | Bolted-on Compose support | Compose-first from scratch | - -### Founding Principles - -1. **You Own the Back Stack** — Navigation state is a `SnapshotStateList` you control directly. Push = `add()`, pop = `removeLastOrNull()`. -2. **Get Out of Your Way** — The library provides open, extensible building blocks rather than a black box. -3. **Pick Your Building Blocks** — Composable components you combine for your specific needs, with recipes for complex scenarios. - -### Data Flow - -``` -User Action → modify backStack (add/remove) → NavDisplay observes change → entryProvider resolves key → NavEntry.content rendered -``` - -This follows **Unidirectional Data Flow (UDF)**, fully aligned with Compose's declarative model. - ---- - -## 2. Core Concepts & API - -### 2.1 Keys (Routes) - -Routes in Nav 3 are simple Kotlin data types that implement `NavKey`: - -```kotlin -@Serializable -data object Home : NavKey - -@Serializable -data class ProductDetail(val id: String) : NavKey - -@Serializable -data object Settings : NavKey -``` - -Keys must be: -- `@Serializable` (for state persistence) -- Implement `NavKey` (marker interface for `rememberNavBackStack`) - -### 2.2 Back Stack - -The back stack is a `SnapshotStateList` — you own it completely: - -```kotlin -// Simple (no persistence across process death) -val backStack = remember { mutableStateListOf(Home) } - -// Persistent (survives config changes + process death) -val backStack = rememberNavBackStack(Home) -``` - -Navigation is just list manipulation: -```kotlin -// Navigate forward -backStack.add(ProductDetail(id = "123")) - -// Navigate back -backStack.removeLastOrNull() -``` - -### 2.3 NavEntry - -`NavEntry` wraps a composable function with its key and optional metadata: - -```kotlin -NavEntry( - key = ProductDetail("123"), - metadata = mapOf("type" to "detail") -) { - ProductDetailScreen() -} -``` - -### 2.4 Entry Provider - -Resolves keys to `NavEntry` objects. Two approaches: - -**Lambda function:** -```kotlin -entryProvider = { key -> - when (key) { - is Home -> NavEntry(key) { HomeScreen() } - is ProductDetail -> NavEntry(key) { ProductDetailScreen(key.id) } - else -> NavEntry(Unit) { Text("Unknown") } - } -} -``` - -**DSL (recommended):** -```kotlin -entryProvider = entryProvider { - entry { HomeScreen() } - entry { key -> ProductDetailScreen(key.id) } -} -``` - -### 2.5 NavDisplay - -The primary UI component that observes the back stack and renders content: - -```kotlin -NavDisplay( - backStack = backStack, - onBack = { backStack.removeLastOrNull() }, - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator() - ), - sceneStrategies = listOf(DialogSceneStrategy()), - entryProvider = entryProvider { - entry { HomeScreen() } - entry { key -> ProductDetailScreen(key.id) } - } -) -``` - -### 2.6 NavEntry Decorators - -Decorators wrap each `NavEntry` with additional logic (state holders, ViewModel stores, logging): - -| Decorator | Purpose | -|-----------|---------| -| `rememberSaveableStateHolderNavEntryDecorator()` | Persists `rememberSaveable` state across config changes and process death. **Should be first in the list.** | -| `rememberViewModelStoreNavEntryDecorator()` | Scopes `ViewModel`s to individual `NavEntry` lifecycle. Requires `androidx.lifecycle:lifecycle-viewmodel-navigation3` dependency. | -| `rememberSharedViewModelStoreNavEntryDecorator()` | Enables ViewModel sharing between parent/child entries. | - -Custom decorator example: -```kotlin -class LoggingDecorator : NavEntryDecorator( - decorate = { entry -> - Log.d("Nav", "Entered ${entry.contentKey}") - entry.Content() - }, - onPop = { key -> Log.d("Nav", "Popped $key") } -) -``` - -### 2.7 Scenes & SceneStrategy - -**Scenes** render one or more `NavEntry` instances. **SceneStrategies** determine which Scene to use based on the current entries. - -**Built-in strategies:** -- `SinglePaneSceneStrategy` — Default, shows topmost entry -- `DialogSceneStrategy` — Renders entries with dialog metadata as overlays -- `ListDetailSceneStrategy` (from `material3-adaptive-navigation3`) — Adaptive list-detail layout - -**Custom strategy example (list-detail):** -```kotlin -class ListDetailSceneStrategy( - val windowSizeClass: WindowSizeClass -) : SceneStrategy { - override fun SceneStrategyScope.calculateScene( - entries: List> - ): Scene? { - if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) - return null - val detailEntry = entries.lastOrNull()?.takeIf { it.metadata.contains(DetailKey) } ?: return null - val listEntry = entries.findLast { it.metadata.contains(ListKey) } ?: return null - return ListDetailScene( - key = listEntry.contentKey, - previousEntries = entries.dropLast(1), - listEntry = listEntry, - detailEntry = detailEntry - ) - } -} -``` - -### 2.8 Metadata - -Arbitrary `Map` attached to `NavEntry` or `Scene` for communication between components: - -```kotlin -// Type-safe metadata DSL (1.1.0-beta01+) -entry( - metadata = metadata { - put(NavDisplay.TransitionKey) { fadeIn() togetherWith fadeOut() } - put(ListDetailSceneStrategy.ListKey, true) - } -) { HomeScreen() } -``` - -### 2.9 Animations - -NavDisplay supports built-in transition animations: - -```kotlin -NavDisplay( - backStack = backStack, - // Global defaults - transitionSpec = { - slideInHorizontally(initialOffsetX = { it }) togetherWith - slideOutHorizontally(targetOffsetX = { -it }) - }, - popTransitionSpec = { - slideInHorizontally(initialOffsetX = { -it }) togetherWith - slideOutHorizontally(targetOffsetX = { it }) - }, - predictivePopTransitionSpec = { - slideInHorizontally(initialOffsetX = { -it }) togetherWith - slideOutHorizontally(targetOffsetX = { it }) - }, - entryProvider = entryProvider { - // Per-entry override via metadata - entry( - metadata = metadata { - put(NavDisplay.TransitionKey) { - slideInVertically(initialOffsetY = { it }) togetherWith - ExitTransition.KeepUntilTransitionsFinished - } - } - ) { ScreenC() } - } -) -``` - -Shared element transitions: -```kotlin -SharedTransitionLayout { - NavDisplay( - sharedTransitionScope = this, - // ... - ) -} -``` - -### 2.10 Deep Linking - -Nav 3 does **not** have built-in deep link support like Nav 2. Instead, you parse intents manually: - -1. Define `DeepLinkPattern` objects mapping URIs to `NavKey` classes -2. Parse incoming `Intent.data` into a `DeepLinkRequest` -3. Match against patterns to produce a `NavKey` -4. Build a synthetic back stack for proper Up navigation - -```kotlin -override fun onCreate(savedInstanceState: Bundle?) { - val uri: Uri? = intent.data - val key: NavKey = uri?.let { parseDeepLink(it) } ?: HomeKey - setContent { - val backStack = rememberNavBackStack(key) - NavDisplay(backStack = backStack, ...) - } -} -``` - -Recipes available: [Basic deep links](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/deeplinks), [Advanced deep links](https://github.com/android/nav3-recipes/tree/main/advanceddeeplinkapp). - ---- - -## 3. Best Practices - -### 3.1 Architecture Pattern - -**Recommended: Navigator + NavigationState pattern** (from official migration guide): - -```kotlin -// NavigationState — holds all navigation state -class NavigationState( - val startRoute: NavKey, - topLevelRoute: MutableState, - val backStacks: Map> -) { - var topLevelRoute: NavKey by topLevelRoute -} - -// Navigator — handles navigation events -class Navigator(val state: NavigationState) { - fun navigate(route: NavKey) { - if (route in state.backStacks.keys) { - state.topLevelRoute = route - } else { - state.backStacks[state.topLevelRoute]?.add(route) - } - } - fun goBack() { /* ... */ } -} -``` - -### 3.2 Multi-Module Navigation - -Nav 3 is designed for modular navigation. Each feature module exposes: -- **`api` module**: Navigation keys (routes) -- **`impl` module**: Entry builders (content) - -```kotlin -// feature/chat/impl -fun EntryProviderScope.chatEntryBuilder() { - entry { key -> ChatScreen(key.conversationId) } - entry { ChatSettingsScreen() } -} - -// app module -NavDisplay( - entryProvider = entryProvider { - chatEntryBuilder() - authEntryBuilder() - settingsEntryBuilder() - } -) -``` - -**With Koin DI** (from nav3-recipes): -```kotlin -// Each feature module provides its entry builder -val chatNavigationModule = module { - factory.() -> Unit>(named("chat")) { - { chatEntryBuilder(get()) } - } -} - -// App module collects all builders -val entryBuilders: List.() -> Unit> = getAll() -NavDisplay( - entryProvider = entryProvider { - entryBuilders.forEach { builder -> this.builder() } - } -) -``` - -### 3.3 ViewModel Scoping - -ViewModels are scoped to `NavEntry` lifecycle via decorators: - -```kotlin -NavDisplay( - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), // Always first - rememberViewModelStoreNavEntryDecorator() - ), - // ... -) -``` - -With this setup: -- `viewModel()` calls inside NavEntry content are scoped to that entry -- ViewModels survive config changes -- ViewModels are cleared when the entry is popped - -**With Koin:** -```kotlin -entry { key -> - val viewModel = koinViewModel() - ChatScreen(viewModel) -} -``` - -### 3.4 Multiple Back Stacks (Bottom Navigation) - -For apps with bottom navigation / top-level routes: - -```kotlin -val topLevelRoutes = setOf(Home, Search, Profile) - -// Each top-level route gets its own persistent back stack -val backStacks = topLevelRoutes.associateWith { rememberNavBackStack(it) } - -// Switch between stacks (state is retained) -var currentTopLevel by remember { mutableStateOf(Home) } -``` - -Recipe: [Multiple back stacks](https://github.com/android/nav3-recipes/tree/main/app/src/main/java/com/example/nav3recipes/multiplestacks) - -### 3.5 Adaptive / Multi-Pane Layouts - -Use Material 3 Adaptive for responsive list-detail: - -```kotlin -val listDetailStrategy = rememberListDetailSceneStrategy() - -NavDisplay( - backStack = backStack, - sceneStrategies = listOf(listDetailStrategy), - entryProvider = entryProvider { - entry( - metadata = ListDetailSceneStrategy.listPane( - detailPlaceholder = { Text("Select a conversation") } - ) - ) { ConversationListScreen() } - - entry( - metadata = ListDetailSceneStrategy.detailPane() - ) { key -> ConversationDetailScreen(key.id) } - } -) -``` - -### 3.6 Testing Navigation - -**Nav 3 has no dedicated testing library like Nav 2's `navigation-testing`.** However, since navigation state is just a list, testing is simpler: - -```kotlin -@Test -fun testNavigation() { - val backStack = mutableStateListOf(Home) - - // Simulate navigation - backStack.add(ProductDetail("123")) - assertEquals(ProductDetail("123"), backStack.last()) - - // Simulate back - backStack.removeLastOrNull() - assertEquals(Home, backStack.last()) -} -``` - -For UI testing, you can use standard Compose testing: -```kotlin -@Test -fun testNavigationUI() { - composeTestRule.setContent { - val backStack = remember { mutableStateListOf(Home) } - NavDisplay( - backStack = backStack, - entryProvider = entryProvider { /* ... */ } - ) - } - // Assert on rendered content -} -``` - -The `SceneStrategyScope` has a no-arg public constructor specifically for testing (added in 1.0.0-beta01). - ---- - -## 4. Migration Guide (Nav 2 → Nav 3) - -### 4.1 Prerequisites - -- `compileSdk` 36+ -- `minSdk` 23+ -- All destinations must be composable functions -- Routes must implement `NavKey` - -### 4.2 Can Nav 2 and Nav 3 Coexist? - -**Yes.** The official migration guide mentions **atomic migration** as the recommended approach, but community sources confirm **incremental migration** is possible: - -- Nav 2 and Nav 3 can operate simultaneously during migration -- Features can be migrated one at a time -- Business logic and ViewModels don't need rewriting - -However, the official migration guide assumes atomic migration. For incremental, you'd maintain a Nav 2 `NavHost` for unmigrated features and a Nav 3 `NavDisplay` for migrated ones, coordinating between them. - -### 4.3 Step-by-Step Migration - -#### Step 1: Add Dependencies - -```toml -[versions] -nav3Core = "1.0.1" -lifecycleViewmodelNav3 = "2.11.0-alpha03" - -[libraries] -androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } -androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } -androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } -``` - -#### Step 2: Update Routes to Implement NavKey - -```kotlin -// Before -@Serializable data object Home -@Serializable data class Chat(val conversationId: String) - -// After -@Serializable data object Home : NavKey -@Serializable data class Chat(val conversationId: String) : NavKey -``` - -#### Step 3: Create NavigationState & Navigator - -See [Section 3.1](#31-architecture-pattern) for the full pattern. - -#### Step 4: Replace NavController Calls - -| Nav 2 | Nav 3 | -|-------|-------| -| `navController.navigate(route)` | `navigator.navigate(route)` or `backStack.add(route)` | -| `navController.popBackStack()` | `navigator.goBack()` or `backStack.removeLastOrNull()` | -| `navController.currentDestination` | `backStack.last()` | -| `currentBackStackEntryAsState()` | Direct state observation of `backStack` | -| `destination.isRouteInHierarchy()` | `key == navigationState.topLevelRoute` | - -#### Step 5: Convert Destinations - -| Nav 2 | Nav 3 | -|-------|-------| -| `composable { }` | `entry { }` | -| `dialog { }` | `entry(metadata = DialogSceneStrategy.dialog()) { }` | -| `navigation(startDestination = ...) { }` | **Delete** — replace references with first child route | -| `NavGraphBuilder` extension | `EntryProviderScope` extension | -| `entry.toRoute()` | `key` parameter: `entry { key -> ... }` | - -**Before:** -```kotlin -NavHost(navController = navController, startDestination = Home) { - composable { HomeScreen() } - composable { entry -> - val route = entry.toRoute() - ChatScreen(route.conversationId) - } - dialog { ConfirmDialogScreen() } -} -``` - -**After:** -```kotlin -NavDisplay( - backStack = backStack, - onBack = { backStack.removeLastOrNull() }, - sceneStrategies = listOf(DialogSceneStrategy()), - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator() - ), - entryProvider = entryProvider { - entry { HomeScreen() } - entry { key -> ChatScreen(key.conversationId) } - entry( - metadata = DialogSceneStrategy.dialog() - ) { ConfirmDialogScreen() } - } -) -``` - -#### Step 6: Remove Nav 2 Dependencies - -Remove all Nav 2 imports, `NavController`, `NavHost`, `NavGraphBuilder`, and gradle dependencies. - -### 4.4 Key Differences to Watch - -1. **No NavController** — State is a mutable list, not a controller object -2. **No nested graphs** — Delete `navigation` blocks entirely -3. **No built-in deep links** — Must implement manually (see recipes) -4. **Route args via key param** — `entry { key -> ... }` instead of `toRoute()` -5. **Dialogs via metadata** — `DialogSceneStrategy.dialog()` metadata instead of `dialog` -6. **Use `rememberSerializable`, not `rememberSaveable`** — For top-level route persistence -7. **Decorators are explicit** — Must add state/ViewModel decorators manually - ---- - -## 5. Gotchas, Footguns & Complications - -### 5.1 Known Issues (from release notes) - -| Issue | Status | Versions | -|-------|--------|----------| -| z-order bug during animation interruptions ([b/459419800](https://issuetracker.google.com/issues/459419800)) | Fixed in 1.1.0-rc01 | | -| Lifecycle not capped at STARTED for entries under overlays ([b/483966071](https://issuetracker.google.com/issues/483966071)) | Fixed in 1.1.0-rc01 | | -| Crash with SharedTransitionLayout + OverlayScene ([b/478664101](https://issuetracker.google.com/issues/478664101)) | Fixed in 1.1.0-alpha04 | | -| IllegalStateException in Android Studio Previews ([b/477149762](https://issuetracker.google.com/issues/477149762)) | Fixed in 1.0.1 / NavigationEvent 1.0.2 | | -| Infinite scene recalculation ([b/418153031](https://issuetracker.google.com/issues/418153031)) | Fixed in 1.0.0-alpha07 | | -| Screen flicker when swapping backStack with animations ([b/450967248](https://issuetracker.google.com/issues/450967248)) | Fixed in 1.0.0-beta01 | | - -### 5.2 Footguns - -1. **Decorator order matters** — `rememberSaveableStateHolderNavEntryDecorator()` must be **first** in the decorators list. Otherwise, `rememberSaveable` won't work inside NavEntry content. - -2. **Animations in adaptive layouts** — When using `ListDetailSceneStrategy` in two-pane mode (>=600dp), transition animations between top-level destinations may not work. The screen "jumps" without animation. - -3. **No built-in deep link handling** — Unlike Nav 2's `deepLinks { }` DSL, you must parse intents and build synthetic back stacks yourself. See the recipes repo for patterns. - -4. **`rememberNavBackStack` requires NavKey** — If you want persistence across process death, all keys must implement `NavKey` and be `@Serializable`. Simple `data object` without `NavKey` will work but won't survive process death. - -5. **compileSdk 36 required** — This is a hard requirement. If your project is on an older compileSdk, you must upgrade. - -6. **No nested navigation (>1 level)** — The migration guide explicitly states: only one level of nesting is supported. Shared destinations (screens moving between different back stacks) are also unsupported. - -7. **`calculateScene` is no longer `@Composable` (since 1.0.0-alpha11)** — Custom SceneStrategies cannot call composable functions in `calculateScene`. Use `remember` in a factory function instead. - -8. **NavBackStack serialization is Android-only** — The built-in `NavBackStackSerializer` is restricted to Android ([b/420443609](https://issuetracker.google.com/issues/420443609)). For KMP, you need polymorphic serialization (see Section 6). - -9. **API churn in 1.1.x** — The 1.1 branch is still in RC. APIs like `SceneStrategy` receiving `List` instead of chained strategies changed in alpha05. Pin to 1.0.1 for stability. - -### 5.3 Things Harder in Nav 3 - -- **Deep linking**: Significantly more manual work required -- **Bottom sheet destinations**: No built-in support; recipe available but requires custom SceneStrategy -- **Fragment/View interop**: Must use Views-in-Compose wrappers; Nav 3 is Compose-only -- **Shared destinations between back stacks**: Not supported -- **Testing**: No dedicated testing artifact; testing is easier conceptually (just a list) but lacks the convenience APIs of `TestNavHostController` - -### 5.4 Things Easier in Nav 3 - -- **Back stack inspection**: It's just a list — fully observable and debuggable -- **Multi-pane/adaptive layouts**: First-class support via Scenes -- **Custom navigation logic**: No fighting the NavController -- **State management**: Aligns perfectly with Compose state -- **ViewModel scoping**: Explicit, clear, and configurable via decorators - ---- - -## 6. KMP / Compose Multiplatform Considerations - -### 6.1 Is Nav 3 Available for Compose Multiplatform? - -**Yes.** JetBrains added Nav 3 support in **Compose Multiplatform 1.10.0** (January 2026) for all platforms: Android, iOS, desktop, and web. - -### 6.2 Dependencies (KMP) - -```toml -[versions] -multiplatform-nav3-ui = "1.0.0-alpha05" - -[libraries] -jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "multiplatform-nav3-ui" } -``` - -Nav 3 ships as two artifacts: -- `navigation3-common` — Core (transitive dependency) -- `navigation3-ui` — NavDisplay and UI components (separate CMP implementation) - -Additional KMP libraries: -```toml -# Material 3 Adaptive -jetbrains-material3-adaptiveNavigation3 = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "compose-multiplatform-adaptive" } - -# ViewModel support -jetbrains-lifecycle-viewmodelNavigation3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "compose-multiplatform-lifecycle" } -``` - -### 6.3 Critical KMP Requirement: Polymorphic Serialization - -**On Android**, Nav 3 uses reflection-based serialization for `NavKey` types. **This does NOT work on non-JVM platforms (iOS, web).** You must use polymorphic serialization. - -`rememberNavBackStack()` has two overloads: -1. **Android-only**: `rememberNavBackStack(startKey)` — uses reflection -2. **Multiplatform**: `rememberNavBackStack(config, startKey)` — requires `SavedStateConfiguration` - -**For KMP, you MUST use the second overload:** - -```kotlin -@Serializable -data object Home : NavKey - -@Serializable -data class Chat(val id: String) : NavKey - -// Required for non-JVM platforms -private val config = SavedStateConfiguration { - serializersModule = SerializersModule { - polymorphic(NavKey::class) { - subclass(Home::class, Home.serializer()) - subclass(Chat::class, Chat.serializer()) - } - } -} - -@Composable -fun App() { - val backStack = rememberNavBackStack(config, Home) - NavDisplay(backStack = backStack, ...) -} -``` - -### 6.4 Multi-Module KMP Pattern - -For our multi-module setup, use sealed interfaces per feature module: - -```kotlin -// feature/auth/api -@Serializable -sealed interface AuthRoute : NavKey - -@Serializable data object Login : AuthRoute -@Serializable data object Register : AuthRoute - -// feature/chat/api -@Serializable -sealed interface ChatRoute : NavKey - -@Serializable data class Conversation(val id: String) : ChatRoute - -// app module — aggregate all feature routes -private val config = SavedStateConfiguration { - serializersModule = SerializersModule { - polymorphic(NavKey::class) { - subclassesOfSealed() - subclassesOfSealed() - } - } -} -``` - -Alternatively, with Koin DI, each module can expose a `SerializersModule` and the app module combines them: - -```kotlin -// Each feature module -val chatSerializerModule = SerializersModule { - polymorphic(NavKey::class) { - subclass(Conversation::class, Conversation.serializer()) - } -} - -// App module -val combinedConfig = SavedStateConfiguration { - serializersModule = chatSerializerModule + authSerializerModule + settingsSerializerModule -} -``` - -### 6.5 Impact on Our Current Setup - -We currently use `org.jetbrains.androidx.navigation:navigation-compose:2.9.2`. For Nav 3: - -| Current (Nav 2 KMP) | Nav 3 KMP | -|---------------------|-----------| -| `org.jetbrains.androidx.navigation:navigation-compose:2.9.2` | `org.jetbrains.androidx.navigation3:navigation3-ui:1.0.0-alpha05` | -| `NavHost` | `NavDisplay` | -| `NavController` | `SnapshotStateList` + custom Navigator | -| Reflection-based routing | **Must use** `SavedStateConfiguration` with polymorphic serialization | -| Direct route access | Key-based entry access | - -**Key considerations:** -- The JetBrains Nav 3 artifact is at **1.0.0-alpha05** (not yet stable for KMP) -- Android-side Nav 3 is stable at 1.0.1 -- We must implement polymorphic serialization for all routes -- Our Koin-based DI can provide serializer modules per feature -- Browser history navigation (for web target) is expected in Nav 3 KMP 1.1.0 - -### 6.6 CMP Nav 3 Recipes - -JetBrains maintains a Compose Multiplatform fork of the nav3-recipes: -- [github.com/terrakok/nav3-recipes](https://github.com/terrakok/nav3-recipes) - ---- - -## 7. Appendix: Version & Dependency Reference - -### AndroidX Dependencies (Android-only) - -```toml -[versions] -nav3Core = "1.0.1" # Stable -# nav3Core = "1.1.0-rc01" # Latest RC -lifecycleViewmodelNav3 = "2.11.0-alpha03" -material3AdaptiveNav3 = "1.3.0-alpha09" -kotlinxSerializationCore = "1.9.0" - -[libraries] -androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } -androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } -androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } -androidx-material3-adaptive-navigation3 = { module = "androidx.compose.material3.adaptive:adaptive-navigation3", version.ref = "material3AdaptiveNav3" } -kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } -``` - -### Compose Multiplatform Dependencies (KMP) - -```toml -[versions] -multiplatform-nav3-ui = "1.0.0-alpha05" -compose-multiplatform-adaptive = "1.3.0-alpha02" -compose-multiplatform-lifecycle = "2.10.0-alpha05" - -[libraries] -jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "multiplatform-nav3-ui" } -jetbrains-material3-adaptiveNavigation3 = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "compose-multiplatform-adaptive" } -jetbrains-lifecycle-viewmodelNavigation3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "compose-multiplatform-lifecycle" } -``` - -### Nav 3 Recipes Repository - -- Android: [github.com/android/nav3-recipes](https://github.com/android/nav3-recipes) (1.2k stars) -- CMP: [github.com/terrakok/nav3-recipes](https://github.com/terrakok/nav3-recipes) - -Available recipes: -- Basic, Saveable back stack, Entry provider DSL -- Deep links (basic + advanced) -- Dialog, BottomSheet, List-Detail, Two-pane, Supporting Pane -- Animations -- Navigation UI, Multiple back stacks -- Conditional navigation -- Modularized navigation (Hilt + Koin) -- ViewModel patterns (basic, Hilt, Koin, shared) -- Retain values for back stack composables -- Returning results (events + state) -- Fragment/View interop - -### Sources - -- [Navigation 3 Overview](https://developer.android.com/guide/navigation/navigation-3) -- [Navigation 3 Basics](https://developer.android.com/guide/navigation/navigation-3/basics) -- [Navigation 3 Migration Guide](https://developer.android.com/guide/navigation/navigation-3/migration-guide) -- [Navigation 3 Save State](https://developer.android.com/guide/navigation/navigation-3/save-state) -- [Navigation 3 Metadata](https://developer.android.com/guide/navigation/navigation-3/metadata) -- [Navigation 3 Modularize](https://developer.android.com/guide/navigation/navigation-3/modularize) -- [Navigation 3 Scenes](https://developer.android.com/guide/navigation/navigation-3/scenes) -- [Navigation 3 Animations](https://developer.android.com/guide/navigation/navigation-3/animate-destinations) -- [Navigation 3 Entry Decorators](https://developer.android.com/guide/navigation/navigation-3/naventrydecorators) -- [Navigation 3 Get Started](https://developer.android.com/guide/navigation/navigation-3/get-started) -- [Navigation 3 Release Notes](https://developer.android.com/jetpack/androidx/releases/navigation3) -- [Navigation 3 in Compose Multiplatform](https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html) -- [Compose Multiplatform 1.10.0 Blog Post](https://blog.jetbrains.com/kotlin/2026/01/compose-multiplatform-1-10-0/) -- [Jetpack Navigation 3 is Stable (Blog)](https://android-developers.googleblog.com/2025/11/jetpack-navigation-3-is-stable.html) -- [Announcing Jetpack Navigation 3 (Blog)](https://android-developers.googleblog.com/2025/05/announcing-jetpack-navigation-3-for-compose.html) -- [nav3-recipes GitHub](https://github.com/android/nav3-recipes) -- [Deep Link Guide](https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md) -- [Shared ViewModel Recipe](https://developer.android.com/guide/navigation/navigation-3/recipes/sharedviewmodel)