Navigation 3 represents a fundamental shift in how Jetpack Compose handles navigation state and offers significant architectural advantages over Navigation 2.
Understand the architectural changes and steps required to migrate a Wear Compose app from Navigation 2 to Navigation 3.
Key advantages of Navigation 3
- Direct Back Stack Control: The
NavBackStackis fundamentally just a mutable list ofNavKeyobjects, representing the history of screens the user has visited. You control it exactly like you would any KotlinMutableList(add,removeLast,clear). You directly manipulate the list to perform navigation actions, such as adding a key to go forward or removing a key to go back. - Compose-First Design: The back stack is modeled as standard observable state. Modifying your navigation history behaves exactly like updating any other Compose state, automatically triggering recomposition to display the current screen.
- Type-Safe by Default: String-based routes are eliminated entirely. Navigation utilizes serializable data objects and data classes.
- Decoupled Presentations (Scene Strategies): The UI transition layer
(
NavDisplayandSwipeDismissableSceneStrategy) is entirely separated from the state tracking (NavBackStack), enabling simpler integration of built-in Wear OS navigation transitions.
Migration steps
1. Update dependencies
Remove the old androidx.wear.compose:compose-navigation dependency and
introduce the new split Navigation 3 dependencies, along with Kotlin
serialization support.
Remove:
implementation("androidx.wear.compose:compose-navigation:...")
Add:
implementation("androidx.navigation3:navigation3-runtime:...") // State logic
implementation("androidx.navigation3:navigation3-ui:...") // Display logic
implementation("androidx.wear.compose:compose-navigation3:...") // Wear gestures
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:...") // Requires compiler plugin
2. Update destinations to implement NavKey
In Navigation 2, you might have used strings or generic objects for routing. In
Navigation 3, you must implement the NavKey marker interface and
annotate every screen object with @Serializable.
Why is this required? To guarantee that the back stack can be saved and
restored across process death, the underlying navigation3-runtime relies on
kotlinx-serialization to serialize the state.
Before (Navigation 2 - Generic Type-Safe Routes):
sealed class Nav2Screen { data object Landing : Nav2Screen() data object List : Nav2Screen() }
After (Navigation 3 - NavKey + Serializable):
@Serializable sealed interface MigrationScreen : NavKey { @Serializable data object Landing : MigrationScreen @Serializable data object List : MigrationScreen }
3. Replace the Routing Logic (NavController to NavBackStack)
Replace your NavController with a NavBackStack initialized via
rememberNavBackStack. You also need to instantiate the
SwipeDismissableSceneStrategy specifically for Wear OS.
Before (Navigation 2):
val navController = rememberSwipeDismissableNavController()
After (Navigation 3):
val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey) val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()
4. Replace NavHost with NavDisplay and the entryProvider DSL
The NavHost container and its internal composable("route") { ... } builder
DSL are replaced by NavDisplay and the entryProvider {
entry<Key> { ... } } DSL.
Before (Navigation 2):
SwipeDismissableNavHost(navController = navController, startDestination = "menu") { composable("menu") { GreetingScreen( onShowList = { navController.navigate("list") } ) } composable("list") { ListScreen() } }
After (Navigation 3):
NavDisplay( backStack = backStack, sceneStrategies = listOf(strategy), entryProvider = entryProvider { entry<MigrationScreen.Landing> { GreetingScreen( onShowList = { backStack.add(MigrationScreen.List) } ) } entry<MigrationScreen.List> { ListScreen() } } )