### Dynamic Feature Manager for Installation Source: https://developer.android.com/guide/navigation/navigation-3/recipes/dynamicfeature?hl=de Manages the installation of dynamic feature modules. It handles registering listeners, tracking download progress, and responding to installation states like downloading, installed, failed, or canceled. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.dynamicfeature import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.retain import androidx.compose.runtime.setValue import com.google.android.play.core.ktx.bytesDownloaded import com.google.android.play.core.ktx.sessionId import com.google.android.play.core.ktx.status import com.google.android.play.core.ktx.totalBytesToDownload import com.google.android.play.core.splitinstall.SplitInstallManagerFactory import com.google.android.play.core.splitinstall.SplitInstallRequest import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus class DynamicFeatureManager(context: Context) { private val splitInstallManager = SplitInstallManagerFactory.create(context) private var listener: SplitInstallStateUpdatedListener? = null var sessionId by mutableStateOf(null) private set var downloadState by mutableStateOf(null) private set fun installModule(moduleName: String, onModuleInstalled: () -> Unit) { if (splitInstallManager.installedModules.contains(moduleName)) { onModuleInstalled() return } listener = SplitInstallStateUpdatedListener { state -> if (state.sessionId == sessionId) { when (state.status) { SplitInstallSessionStatus.DOWNLOADING -> { downloadState = DownloadState( bytesDownloaded = state.bytesDownloaded, totalBytesToDownload = state.totalBytesToDownload, ) } SplitInstallSessionStatus.INSTALLED -> { downloadState = null sessionId = null unregisterListener() onModuleInstalled() } SplitInstallSessionStatus.FAILED, SplitInstallSessionStatus.CANCELED -> { downloadState = null sessionId = null unregisterListener() } else -> {} } } }.also(splitInstallManager::registerListener) splitInstallManager.startInstall( SplitInstallRequest.newBuilder().addModule(moduleName).build() ).addOnSuccessListener { sessionId = it } } fun cancelInstallModule() { sessionId?.let { splitInstallManager.cancelInstall(it).addOnSuccessListener { downloadState = null sessionId = null unregisterListener() } } } private fun unregisterListener() { listener?.let(splitInstallManager::unregisterListener) } data class DownloadState( val bytesDownloaded: Long, val totalBytesToDownload: Long, ) } ``` -------------------------------- ### Example Usage of Navigation Decorator Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=de This is a boilerplate code snippet demonstrating the setup for the Navigation Scene Decorator. It includes necessary imports and basic structure for a navigation component. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.navscenedecorator import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets ``` -------------------------------- ### NavigationSceneDecorator Strategy Implementation Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=pl Example of implementing the NavigationSceneDecorator strategy with different routes and content. ```kotlin onSubRouteClick: () -> Unit, ) { entry { ContentMauve("Route C") { Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = dropUnlessResumed { onSubRouteClick() }) { Text("Go to C1") } } } } entry { ContentOrange("Route C1") { var count by rememberSaveable { mutableIntStateOf(0) } Button(onClick = { count++ }) { Text("Value: $count") } } } } ``` -------------------------------- ### Navigation Scene Decorator Example Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=id Demonstrates the usage of NavigationSceneDecoratorStrategy to manage different routes and their content within a navigation flow. It includes examples of navigating between routes and displaying dynamic content. ```kotlin class NavigationSceneDecoratorStrategy( onSubRouteClick: () -> Unit, ) { entry { ContentMauve("Route C") { Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = dropUnlessResumed { onSubRouteClick() }) { Text("Go to C1") } } } } entry { ContentOrange("Route C1") { var count by rememberSaveable { mutableIntStateOf(0) } Button(onClick = { count++ }) { Text("Value: $count") } } } } ``` -------------------------------- ### MainActivity Setup for Deeplink Handling Source: https://developer.android.com/guide/navigation/navigation-3/recipes/deeplinks-basic?hl=id This is the main activity file for the deeplink recipe. It sets up the content and demonstrates how an app can handle incoming deeplink requests by configuring the navigation stack based on parsed deeplinks. ```kotlin package com.example.nav3recipes.deeplink.basic import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.core.net.toUri import androidx.lifecycle.compose.dropUnlessResumed import com.example.nav3recipes.common.deeplink.EMPTY import com.example.nav3recipes.common.deeplink.EntryScreen import com.example.nav3recipes.common.deeplink.FIRST_NAME_JOHN import com.example.nav3recipes.common.deeplink.FIRST_NAME_JULIE import com.example.nav3recipes.common.deeplink.FIRST_NAME_MARY import com.example.nav3recipes.common.deeplink.FIRST_NAME_TOM import com.example.nav3recipes.common.deeplink.LOCATION_BC import com.example.nav3recipes.common.deeplink.LOCATION_BR import com.example.nav3recipes.common.deeplink.LOCATION_CA import com.example.nav3recipes.common.deeplink.LOCATION_US import com.example.nav3recipes.common.deeplink.MenuDropDown import com.example.nav3recipes.common.deeplink.MenuTextInput import com.example.nav3recipes.common.deeplink.PaddedButton import com.example.nav3recipes.common.deeplink.TextContent import com.example.nav3recipes.deeplink.basic.ui.PATH_BASE import com.example.nav3recipes.deeplink.basic.ui.PATH_INCLUDE import com.example.nav3recipes.deeplink.basic.ui.PATH_SEARCH import com.example.nav3recipes.deeplink.basic.ui.STRING_LITERAL_HOME import com.example.nav3recipes.ui.setEdgeToEdgeConfig /** * This activity allows the user to create a deep link and make a request with it. * * **HOW THIS RECIPE WORKS** it consists of two activities - [CreateDeepLinkActivity] to construct * and trigger the deeplink request, and the [MainActivity] to show how an app can handle * that request. * * **DEMONSTRATED FORMS OF DEEPLINK** The [MainActivity] has a several backStack keys to * demonstrate different types of supported deeplinks: * 1. [HomeKey] - deeplink with an exact url (no deeplink arguments) ``` -------------------------------- ### Example DeepLinkPattern Usage Source: https://developer.android.com/guide/navigation/navigation-3/recipes/deeplinks-basic?hl=de Illustrates how to create `DeepLinkPattern` instances for different URI patterns. This setup is crucial for handling various deep link structures, including those with path arguments. ```kotlin val deeplink1 = www.nav3recipes.com/home val deeplink2 = www.nav3recipes.com/profile/{userId} ``` ```kotlin val parsedDeeplink1 = DeepLinkPattern(T.serializer(), deeplink1) val parsedDeeplink2 = DeepLinkPattern(T.serializer(), deeplink2) ``` -------------------------------- ### Koin Modular Activity Setup Source: https://developer.android.com/guide/navigation/navigation-3/recipes/modular-koin Sets up the main activity for a modular Koin Navigation 3 application. It overrides the default Koin context to use a local instance and injects the Navigator for use in the NavDisplay. ```kotlin package com.example.nav3recipes.modular.koin import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.ui.Modifier import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.ui.setEdgeToEdgeConfig import org.koin.android.ext.android.inject import org.koin.android.scope.AndroidScopeComponent import org.koin.androidx.compose.navigation3.getEntryProvider import org.koin.androidx.scope.activityRetainedScope import org.koin.core.Koin import org.koin.core.annotation.KoinExperimentalAPI import org.koin.core.component.KoinComponent import org.koin.core.scope.Scope import org.koin.dsl.koinApplication /** * This recipe demonstrates how to use a modular approach with Navigation 3, * where different parts of the application are defined in separate modules and injected * into the main app using Koin. * * Features (Conversation and Profile) are split into two modules: * - api: defines the public facing routes for this feature * - impl: defines the entryProviders for this feature, these are injected into the app's main activity * The common module defines: * - a common navigator class that exposes a back stack and methods to modify that back stack * - a type that should be used by feature modules to inject entryProviders into the app's main activity * The app module creates the navigator by supplying a start destination and provides this navigator * to the rest of the app module (i.e. MainActivity) and the feature modules. */ @OptIn(KoinExperimentalAPI::class) class KoinModularActivity : ComponentActivity(), AndroidScopeComponent, KoinComponent { // Local Koin Context Instance companion object { private val localKoin = koinApplication { modules(appModule) }.koin } // Override default Koin context to use the local one override fun getKoin(): Koin = localKoin override val scope : Scope by activityRetainedScope() val navigator: Navigator by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setEdgeToEdgeConfig() setContent { Scaffold { paddingValues -> NavDisplay( backStack = navigator.backStack, modifier = Modifier.padding(paddingValues), ``` -------------------------------- ### MainActivity Setup for Deeplinks Source: https://developer.android.com/guide/navigation/navigation-3/recipes/deeplinks-basic?hl=de This Kotlin code sets up the main activity for handling deeplinks. It imports necessary components for navigation, UI, and deeplink handling, and configures the activity to use edge-to-edge display. ```kotlin package com.example.nav3recipes.deeplink.basic import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.core.net.toUri import androidx.lifecycle.compose.dropUnlessResumed import com.example.nav3recipes.common.deeplink.EMPTY import com.example.nav3recipes.common.deeplink.EntryScreen import com.example.nav3recipes.common.deeplink.FIRST_NAME_JOHN import com.example.nav3recipes.common.deeplink.FIRST_NAME_JULIE import com.example.nav3recipes.common.deeplink.FIRST_NAME_MARY import com.example.nav3recipes.common.deeplink.FIRST_NAME_TOM import com.example.nav3recipes.common.deeplink.LOCATION_BC import com.example.nav3recipes.common.deeplink.LOCATION_BR import com.example.nav3recipes.common.deeplink.LOCATION_CA import com.example.nav3recipes.common.deeplink.LOCATION_US import com.example.nav3recipes.common.deeplink.MenuDropDown import com.example.nav3recipes.common.deeplink.MenuTextInput import com.example.nav3recipes.common.deeplink.PaddedButton import com.example.nav3recipes.common.deeplink.TextContent import com.example.nav3recipes.deeplink.basic.ui.PATH_BASE import com.example.nav3recipes.deeplink.basic.ui.PATH_INCLUDE import com.example.nav3recipes.deeplink.basic.ui.PATH_SEARCH import com.example.nav3recipes.deeplink.basic.ui.STRING_LITERAL_HOME import com.example.nav3recipes.ui.setEdgeToEdgeConfig /** * This activity allows the user to create a deep link and make a request with it. * * **HOW THIS RECIPE WORKS** it consists of two activities - [CreateDeepLinkActivity] to construct * and trigger the deeplink request, and the [MainActivity] to show how an app can handle * that request. * * **DEMONSTRATED FORMS OF DEEPLINK** The [MainActivity] has a several backStack keys to * demonstrate different types of supported deeplinks: ``` -------------------------------- ### SharedViewModelActivity Setup Source: https://developer.android.com/guide/navigation/navigation-3/recipes/sharedviewmodel?hl=id This activity sets up the navigation graph using `rememberNavBackStack` and applies decorators like `rememberSaveableStateHolderNavEntryDecorator` and `rememberSharedViewModelStoreNavEntryDecorator`. It defines entries for ParentScreen, ChildScreen, and StandaloneScreen. ```kotlin /* * Copyright 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.sharedviewmodel import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.content.ContentBlue import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.content.ContentRed import com.example.nav3recipes.ui.setEdgeToEdgeConfig import kotlinx.serialization.Serializable @Serializable private data object ParentScreen : NavKey @Serializable private data object ChildScreen : NavKey @Serializable private data object StandaloneScreen : NavKey class SharedViewModelActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setEdgeToEdgeConfig() super.onCreate(savedInstanceState) setContent { val backStack = rememberNavBackStack(ParentScreen) NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberSharedViewModelStoreNavEntryDecorator(), ), entryProvider = entryProvider { entry( clazzContentKey = { key -> key.toContentKey() }, ) { val viewModel = viewModel() ContentRed("Parent screen") { Button(onClick = { viewModel.count++ }) { Text("Count: ${viewModel.count}") } Button(onClick = dropUnlessResumed { backStack.add(ChildScreen) }) { Text("View child screen") } } } entry( metadata = SharedViewModelStoreNavEntryDecorator.parent( ParentScreen.toContentKey() ) ) { val parentViewModel = viewModel( viewModelStoreOwner = LocalSharedViewModelStoreOwner.current ) val standaloneViewModel = viewModel() ``` -------------------------------- ### Integrating ListDetailSceneStrategy in NavDisplay Source: https://developer.android.com/guide/navigation/navigation-3/scenes Demonstrates how to use the `ListDetailSceneStrategy` within a `NavDisplay` by providing the strategy and associating metadata with navigation entries. This snippet shows the setup for `ConversationList` and `ConversationDetail` entries. ```kotlin // Define your navigation keys @Serializable data object ConversationList : NavKey @Serializable data class ConversationDetail(val id: String) : NavKey @Composable fun MyAppContent() { val backStack = rememberNavBackStack(ConversationList) val listDetailStrategy = rememberListDetailSceneStrategy() NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry( metadata = ListDetailSceneStrategy.listPane() ) { Column(modifier = Modifier.fillMaxSize()) { Text(text = "I'm a Conversation List") Button(onClick = { backStack.addDetail(ConversationDetail("123")) }) { Text(text = "Open detail") } } } entry( metadata = ListDetailSceneStrategy.detailPane() ) { Text(text = "I'm a Conversation Detail") } } ) } ``` -------------------------------- ### Responsive Navigation Scene Decorator Activity Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator Demonstrates the setup of a responsive navigation scene decorator within an Android Activity. It utilizes SharedTransitionLayout and custom navigation state management. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.navscenedecorator import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.SharedTransitionLayout import androidx.compose.runtime.remember import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.scenes.listdetail.rememberListDetailSceneStrategy import com.example.nav3recipes.ui.setEdgeToEdgeConfig class ResponsiveNavigationSceneDecoratorActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setEdgeToEdgeConfig() setContent { SharedTransitionLayout { val navigationState = rememberNavigationState( startRoute = RouteA, topLevelRoutes = setOf(RouteA, RouteB, RouteC) ) val navigator = remember(navigationState) { Navigator(navigationState) } val listDetailStrategy = rememberListDetailSceneStrategy() val responsiveNavigationSceneDecoratorStrategy = rememberResponsiveNavigationSceneDecoratorStrategy( navBar = { NavBar(NAV_ITEMS, navigator) }, navRail = { NavRail(NAV_ITEMS, navigator) }, sharedTransitionScope = this ) val entryProvider = entryProvider { featureASection { id -> navigator.navigate(RouteA1(id)) } featureBSection { navigator.navigate(RouteB1) } featureCSection { navigator.navigate(RouteC1) } } NavDisplay( entries = navigationState.toEntries(entryProvider), sceneDecoratorStrategies = listOf(responsiveNavigationSceneDecoratorStrategy), sceneStrategies = listOf(listDetailStrategy), sharedTransitionScope = this, onBack = navigator::goBack ) } } } } ResponsiveNavigationSceneDecoratorActivity.kt ``` -------------------------------- ### Setting up Navigation with Deeplink Handling Source: https://developer.android.com/guide/navigation/navigation-3/recipes/deeplinks-basic?hl=id This snippet demonstrates how to set up the main content of the activity, including remembering the navigation backstack and providing entry points for different deeplink keys. It configures the `NavDisplay` component with various entry providers for different key types. ```kotlin setContent { val backStack: NavBackStack = rememberNavBackStack(key) NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = entryProvider { entry { key -> EntryScreen(key.name) { TextContent("") } } entry { key -> EntryScreen("${key.name} : ${key.filter}") { TextContent("") val list = when { key.filter.isEmpty() -> LIST_USERS key.filter == UsersKey.FILTER_OPTION_ALL -> LIST_USERS else -> LIST_USERS.take(5) } FriendsList(list) } } entry { search -> EntryScreen(search.name) { TextContent("") val matchingUsers = LIST_USERS.filter { (search.firstName == null || it.firstName == search.firstName) && (search.location == null || it.location == search.location) && (search.ageMin == null || it.age >= search.ageMin) && (search.ageMax == null || it.age <= search.ageMax) } FriendsList(matchingUsers) } } } ) } ``` -------------------------------- ### Activity demonstrating ListDetailSceneStrategy Source: https://developer.android.com/guide/navigation/navigation-3/custom-layouts This `Activity` shows how to set up a `NavDisplay` with `ListDetailSceneStrategy` to manage list-detail content. It defines navigation entries for product lists, product details, and profiles, specifying their pane roles using metadata. ```kotlin @Serializable object ProductList : NavKey @Serializable data class ProductDetail(val id: String) : NavKey @Serializable data object Profile : NavKey class MaterialListDetailActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3AdaptiveApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Scaffold { paddingValues -> val backStack = rememberNavBackStack(ProductList) val listDetailStrategy = rememberListDetailSceneStrategy() NavDisplay( backStack = backStack, modifier = Modifier.padding(paddingValues), onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry( metadata = ListDetailSceneStrategy.listPane( detailPlaceholder = { ContentYellow("Choose a product from the list") } ) ) { ContentRed("Welcome to Nav3") { Button(onClick = { backStack.add(ProductDetail("ABC")) }) { Text("View product") } } } entry( metadata = ListDetailSceneStrategy.detailPane() ) { product -> ContentBlue("Product ${product.id} ", Modifier.background(PastelBlue)) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { backStack.add(Profile) }) { Text("View profile") } } } } entry( metadata = ListDetailSceneStrategy.extraPane() ) { ContentGreen("Profile") } } ) } } } } ``` -------------------------------- ### Composable for Retaining Dynamic Feature Manager Source: https://developer.android.com/guide/navigation/navigation-3/recipes/dynamicfeature?hl=de Provides a composable function to retain an instance of `DynamicFeatureManager` across recompositions. It uses `LocalContext` to get the application context. ```kotlin import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.retain import androidx.compose.ui.platform.LocalContext @Composable fun retainDynamicFeatureManager(): DynamicFeatureManager { val applicationContext = LocalContext.current.applicationContext return retain { DynamicFeatureManager(applicationContext) } } ``` -------------------------------- ### Activity demonstrating ListDetailSceneStrategy Source: https://developer.android.com/guide/navigation/navigation-3/scenes This snippet shows a sample `Activity` that utilizes `ListDetailSceneStrategy` to manage list-detail content. It defines navigation entries for product lists, product details, and profiles, adapting their display based on pane metadata. ```kotlin @Serializable object ProductList : NavKey @Serializable data class ProductDetail(val id: String) : NavKey @Serializable data object Profile : NavKey class MaterialListDetailActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3AdaptiveApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Scaffold { paddingValues -> val backStack = rememberNavBackStack(ProductList) val listDetailStrategy = rememberListDetailSceneStrategy() NavDisplay( backStack = backStack, modifier = Modifier.padding(paddingValues), onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry( metadata = ListDetailSceneStrategy.listPane( detailPlaceholder = { ContentYellow("Choose a product from the list") } ) ) { ContentRed("Welcome to Nav3") { Button(onClick = { backStack.add(ProductDetail("ABC")) }) { Text("View product") } } } entry( metadata = ListDetailSceneStrategy.detailPane() ) { product -> ContentBlue("Product ${product.id} ", Modifier.background(PastelBlue)) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { backStack.add(Profile) }) { Text("View profile") } } } } entry( metadata = ListDetailSceneStrategy.extraPane() ) { ContentGreen("Profile") } } ) } } } } ``` -------------------------------- ### Refactor NavHost to EntryProviderScope Source: https://developer.android.com/guide/navigation/navigation-3/migration-guide This snippet shows the transformation of a `NavHost` definition using `composable` and `navigation` into an `EntryProviderScope` with `entry` definitions. It demonstrates how to move destinations and adapt route parameters. ```kotlin import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.dialog import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import androidx.navigation.toRoute @Serializable data object BaseRouteA @Serializable data class RouteA(val id: String) @Serializable data object BaseRouteB @Serializable data object RouteB @Serializable data object RouteD NavHost(navController = navController, startDestination = BaseRouteA){ composable{ val id = entry.toRoute().id ScreenA(title = "Screen has ID: $id") } featureBSection() dialog{ ScreenD() } } fun NavGraphBuilder.featureBSection() { navigation(startDestination = RouteB) { composable { ScreenB() } } } ``` ```kotlin import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.scene.DialogSceneStrategy @Serializable data class RouteA(val id: String) : NavKey @Serializable data object RouteB : NavKey @Serializable data object RouteD : NavKey val entryProvider = entryProvider { entry{ key -> ScreenA(title = "Screen has ID: ${key.id}") } featureBSection() entry(metadata = DialogSceneStrategy.dialog()){ ScreenD() } } fun EntryProviderScope.featureBSection() { entry { ScreenB() } } ``` -------------------------------- ### Create Persistent Navigation State Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=fr Use `rememberNavigationState` to create a navigation state that persists across configuration changes and process death. It requires a starting route and a set of top-level routes. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.navscenedecorator import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSerializable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberDecoratedNavEntries import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.runtime.serialization.NavKeySerializer import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer import com.example.nav3recipes.conditional.rememberNavBackStack /** * Create a navigation state that persists config changes and process death. */ @Composable fun rememberNavigationState( startRoute: NavKey, topLevelRoutes: Set ): NavigationState { val topLevelRoute = rememberSerializable( startRoute, topLevelRoutes, serializer = MutableStateSerializer(NavKeySerializer()) ) { mutableStateOf(startRoute) } val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) } return remember(startRoute, topLevelRoutes) { NavigationState( startRoute = startRoute, topLevelRoute = topLevelRoute, backStacks = backStacks ) } } /** * State holder for navigation state. * * @param startRoute - the start route. The user will exit the app through this route. * @param topLevelRoute - the current top level route * @param backStacks - the back stacks for each top level route */ class NavigationState( val startRoute: NavKey, topLevelRoute: MutableState, val backStacks: Map> ) { var topLevelRoute: NavKey by topLevelRoute val stacksInUse: get() = if (topLevelRoute == startRoute) { listOf(startRoute) } else { listOf(startRoute, topLevelRoute) } } /** * Convert NavigationState into NavEntries. */ @Composable fun NavigationState.toEntries( entryProvider: (NavKey) -> NavEntry ): SnapshotStateList> { val decoratedEntries = backStacks.mapValues { (_, stack) -> val decorators = listOf( rememberSaveableStateHolderNavEntryDecorator() ) rememberDecoratedNavEntries( backStack = stack, entryDecorators = decorators, entryProvider = entryProvider ) } return stacksInUse.flatMap { decoratedEntries[it] ?: emptyList() }.toMutableStateList() } NavigationState.kt ``` -------------------------------- ### Koin App Module Definition Source: https://developer.android.com/guide/navigation/navigation-3/recipes/modular-koin Defines the Koin application module, including feature modules and a scoped Navigator. The Navigator is retained at the activity level and initialized with a ConversationList start destination. ```kotlin package com.example.nav3recipes.modular.koin import org.koin.androidx.scope.dsl.activityRetainedScope import org.koin.dsl.module val appModule = module { includes(profileModule,conversationModule) activityRetainedScope { scoped { Navigator(startDestination = ConversationList) } } } ``` -------------------------------- ### Instantiate NavigationState and Navigator Source: https://developer.android.com/guide/navigation/navigation-3/migration-guide Create instances of NavigationState and Navigator with the same scope as your NavController. ```kotlin val navigationState = rememberNavigationState( startRoute = , topLevelRoutes = ) val navigator = remember { Navigator(navigationState) } ``` -------------------------------- ### Create NavDisplay with Entry Provider Source: https://developer.android.com/guide/navigation/navigation-3/basics Construct a `NavDisplay` composable to observe and render the app's navigation state. It requires the back stack, an entry provider, and an optional `onBack` lambda. ```kotlin data object Home data class Product(val id: String) @Composable fun NavExample() { val backStack = remember { mutableStateListOf(Home) } NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is Home -> NavEntry(key) { ContentGreen("Welcome to Nav3") { Button(onClick = { backStack.add(Product("123")) }) { Text("Click to navigate") } } } is Product -> NavEntry(key) { ContentBlue("Product ${key.id} ") } else -> NavEntry(Unit) { Text("Unknown route") } } } ) } BasicSnippets.kt ``` -------------------------------- ### Replace NavHost with NavDisplay Source: https://developer.android.com/guide/navigation/navigation-3/migration-guide This snippet demonstrates replacing the `NavHost` component with `NavDisplay`. It shows how to configure `NavDisplay` with navigation entries, an onBack callback, and scene strategies for dialogs. ```kotlin import androidx.navigation3.ui.NavDisplay NavDisplay( entries = navigationState.toEntries(entryProvider), onBack = { navigator.goBack() }, sceneStrategies = remember { listOf(DialogSceneStrategy()) } ) ``` -------------------------------- ### Configure app/build.gradle.kts for Navigation 3 Source: https://developer.android.com/guide/navigation/navigation-3/get-started Apply the Kotlinx Serialization plugin if needed and declare the Navigation 3 and its optional dependencies in your app's build file. ```gradle plugins { ... // Optional, provides the @Serialize annotation for autogeneration of Serializers. alias(libs.plugins.jetbrains.kotlin.serialization) } dependencies { ... implementation(libs.androidx.navigation3.ui) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.lifecycle.viewmodel.navigation3) implementation(libs.androidx.material3.adaptive.navigation3) implementation(libs.kotlinx.serialization.core) } ``` -------------------------------- ### Basic Imports for Navigation Components Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=hi Essential imports for using navigation components, including LocalNavAnimatedContentScope for animated content and WindowSizeClass for responsive layouts. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.navscenedecorator import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxHeight ``` -------------------------------- ### ResponsiveNavigationScene Implementation Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=de Defines a Scene that adapts its layout (Row for larger screens, Column for smaller screens) based on window size class and integrates shared element transitions for navigation bars and rails. ```kotlin import androidx.navigation3.scene.SceneDecoratorStrategyScope import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.window.core.layout.WindowSizeClass data class ResponsiveNavigationScene( private val scene: Scene, private val sharedTransitionScope: SharedTransitionScope, private val windowSizeClass: WindowSizeClass, private val navBarContent: @Composable (() -> Unit), private val navRailContent: @Composable (() -> Unit), ) : Scene by scene { override val key = scene::class to scene.key override val content = @Composable { val animatedContentScope = LocalNavAnimatedContentScope.current val isMovableContentCaller = animatedContentScope.transition.targetState == EnterExitState.Visible with(sharedTransitionScope) { if (windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND)) { Row(Modifier.fillMaxSize()) { Box( modifier = Modifier .cacheSize(!isMovableContentCaller) .sharedElement( rememberSharedContentState("nav-rail"), animatedContentScope ) ) { if (isMovableContentCaller) { navRailContent() } } Box(modifier = Modifier.weight(1f)) { scene.content() } } } else { Column(Modifier.fillMaxSize()) { Box(modifier = Modifier.weight(1f)) { scene.content() } Box( modifier = Modifier .cacheSize(!isMovableContentCaller) .sharedElement( rememberSharedContentState("nav-bar"), animatedContentScope ) ) { if (isMovableContentCaller) { navBarContent() } } } } } } } ``` -------------------------------- ### Using ListDetailSceneStrategy in NavDisplay Source: https://developer.android.com/guide/navigation/navigation-3/custom-layouts Demonstrates how to integrate the custom ListDetailSceneStrategy into your NavDisplay by providing metadata for list and detail entries and configuring the scene strategies. ```kotlin // Define your navigation keys @Serializable data object ConversationList : NavKey @Serializable data class ConversationDetail(val id: String) : NavKey @Composable fun MyAppContent() { val backStack = rememberNavBackStack(ConversationList) val listDetailStrategy = rememberListDetailSceneStrategy() NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry( metadata = ListDetailSceneStrategy.listPane() ) { Column(modifier = Modifier.fillMaxSize()) { Text(text = "I'm a Conversation List") Button(onClick = { backStack.addDetail(ConversationDetail("123")) }) { Text(text = "Open detail") } } } entry( metadata = ListDetailSceneStrategy.detailPane() ) { Text(text = "I'm a Conversation Detail") } } ) } ``` -------------------------------- ### Use Entry Provider DSL Source: https://developer.android.com/guide/navigation/navigation-3/basics Simplify lambda functions for entry providers using the `entryProvider` builder function. This avoids manual key type testing and includes default fallback behavior. ```kotlin entryProvider = entryProvider { entry { Text("Product List") } entry( metadata = mapOf("extraDataKey" to "extraDataValue") ) { key -> Text("Product ${key.id} ") } } BasicSnippets.kt ``` -------------------------------- ### Basic Imports for Navigation Scene Decorator Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=vi Provides essential imports for implementing the Navigation Scene Decorator, including layout components and window size classes. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.nav3recipes.navscenedecorator import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize ``` -------------------------------- ### Add Navigation 3 Libraries to libs.versions.toml Source: https://developer.android.com/guide/navigation/navigation-3/get-started Configure your project's version catalog to include Navigation 3 core and optional libraries. Ensure your compile SDK is set to 36 or later. ```toml [versions] nav3Core = "1.1.2" lifecycleViewmodelNav3 = "2.11.0-beta02" kotlinSerialization = "2.2.21" kotlinxSerializationCore = "1.9.0" material3AdaptiveNav3 = "1.3.0-beta02" [libraries] # Core Navigation 3 libraries androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } # Optional add-on libraries androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } androidx-material3-adaptive-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "material3AdaptiveNav3" } [plugins] # Optional plugins jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization"} ``` ```toml [versions] compileSdk = "36" ``` -------------------------------- ### Add Navigation 3 Core Dependencies Source: https://developer.android.com/guide/navigation/navigation-3/migration-guide Add the core Navigation 3 runtime and UI libraries to your project's version catalog and build script. Ensure your project's minSdk is at least 23 and compileSdk is 36. ```gradle [versions] nav3Core = "1.0.0" # If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library lifecycleViewmodelNav3 = "2.10.0-rc01" [libraries] # Core Navigation 3 libraries androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } # Add-on libraries (only add if you need them) androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } ``` ```gradle dependencies { implementation(libs.androidx.navigation3.ui) implementation(libs.androidx.navigation3.runtime) // If using the ViewModel add-on library implementation(libs.androidx.lifecycle.viewmodel.navigation3) } ``` -------------------------------- ### Copyright and License Header Source: https://developer.android.com/guide/navigation/navigation-3/recipes/navscenedecorator?hl=zh-CN Standard Apache 2.0 license header for the Kotlin source file. ```kotlin /* * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ ```