Navigation 3 is a navigation library designed from the ground up for Jetpack Compose. This guide explains how to implement Navigation 3 in Wear OS applications.
Core Concepts
NavKey: A type-safe, serializable identifier for a destination (screen) in your app.NavBackStack: A mutable list ofNavKeyinstances representing the navigation history. You push and pop items directly from this list.rememberNavBackStack: A composable that creates and persists the back stack across configuration changes and process death.NavDisplay: The core UI component that observes the back stack and renders the active screen.EntryProvider: A mapping DSL that links aNavKeyto its actual@ComposableUI.SwipeDismissableSceneStrategy: The Wear-specific strategy that wraps your screens in a swipe-to-dismiss gesture and handles built-in back animations.
Step 1: Add dependencies
Add the required Navigation 3, Wear Compose, and Serialization dependencies to your project.
Groovy
dependencies { // Core Navigation 3 APIs implementation "androidx.navigation3:navigation3-runtime:1.2.0-alpha02" implementation "androidx.navigation3:navigation3-ui:1.2.0-alpha02" // Wear OS specific Navigation 3 integration implementation "androidx.wear.compose:compose-navigation3:1.6.1" // Kotlinx Serialization for type-safe routing implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0" }
Kotlin
dependencies { // Core Navigation 3 APIs implementation("androidx.navigation3:navigation3-runtime:1.2.0-alpha02") implementation("androidx.navigation3:navigation3-ui:1.2.0-alpha02") // Wear OS specific Navigation 3 integration implementation("androidx.wear.compose:compose-navigation3:1.6.1") // Kotlinx Serialization for type-safe routing implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0") }
Step 2: Define destinations (NavKeys)
Screens are defined as strongly typed, serializable objects or data classes that
implement the NavKey interface.
@Serializable sealed interface Screen : NavKey { @Serializable data object Home : Screen @Serializable data class Details(val itemId: String) : Screen }
Step 3: Setup NavDisplay and the back stack
At the root of your application, initialize the back stack and the Wear OS scene
strategy, then plug them into NavDisplay.
// 1. Create the persistent back stack starting at the Home screen val backStack = rememberNavBackStack(Screen.Home) // 2. Initialize the Wear OS swipe-to-dismiss strategy val strategy = rememberSwipeDismissableSceneStrategy<NavKey>() // 3. Render the NavDisplay NavDisplay( backStack = backStack, sceneStrategies = listOf(strategy), entryProvider = entryProvider { // 4. Map keys to Composables entry<Screen.Home> { HomeScreen( onNavigateToDetails = { id -> backStack.add(Screen.Details(id)) } ) } entry<Screen.Details> { key -> DetailsScreen( itemId = key.itemId, onBack = { backStack.removeAt(backStack.lastIndex) } ) } } )
Step 4: Perform navigation actions
Because the back stack is just a customized MutableList, navigation is
incredibly straightforward. You perform operations directly on the backStack
instance:
- Navigate Forward:
backStack.add(Screen.Details("123")) - Navigate Back:
backStack.removeLast()orbackStack.removeLastOrNull() - Clear and Reset:
backStack.clear(); backStack.add(Screen.Home)(or use list operations to replace the stack).
Step 5: (Optional) Scope ViewModels to destinations
By default, ViewModels are scoped to the Activity. Navigation 3
provides a specific artifact (lifecycle-viewmodel-navigation3) to safely
scope a ViewModel to a NavEntry on the back stack. When the destination
is popped off the back stack, the ViewModel is cleared.
Add the dependency:
implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:...")Add the ViewModel store decorator to your
NavDisplay'sentryDecorators. You must also explicitly include theSaveableStateHolderNavEntryDecoratorwhen providing custom decorators to retain ComposerememberSaveablestate:NavDisplay( backStack = backStack, sceneStrategies = listOf(strategy), entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator() ), entryProvider = entryProvider { entry<Screen.Home> { // Any viewModel() requested here will be scoped to this NavEntry val viewModel: HomeViewModel = viewModel() } } )