### Install APKS using bundletool Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Install the converted APKS file onto a connected device or emulator using bundletool. ```shell java -jar bundletool.jar install-apks --apks app-debug.apks ``` -------------------------------- ### Manage Dynamic Feature Module Installation Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Use DynamicFeatureManager to install dynamic feature modules. The installModule method handles installation and provides a callback that executes once the module is installed or immediately if already installed. ```kotlin // Inside :app module // Initialize the manager val dynamicFeatureManager = retainDynamicFeatureManager() // E.g. in a Button's onClick dynamicFeatureManager.installModule( moduleName = ExampleModule.moduleName, onModuleInstalled = { backStack.add(ExampleModule.Screen) } ) ``` -------------------------------- ### Display Dynamic Feature Installation Progress Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Monitor the installation progress of dynamic feature modules by attaching the DynamicFeatureManager to the DynamicFeatureDownloadProgressDialog composable. ```kotlin DynamicFeatureDownloadProgressDialog(dynamicFeatureManager) ``` -------------------------------- ### Navigate Up by Restarting Activity in New Task Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Implement the navigateUp function to restart the activity in a new task using the created TaskStackBuilder. Ensure the current activity is finished before starting the new task. ```kotlin fun navigateUp( // pass in required context, activity etc ) { if (onOriginalTask) { val taskStackBuilder = createTaskStackBuilder(deeplinkKey, activity, context) // ensure current activity is finished activity?.finish() // invoke restart taskStackBuilder.startActivities() } } ``` -------------------------------- ### Build Dynamic Entries in App Module Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md In the main app module, use the buildDynamicEntries function to resolve and call the DynamicModuleEntryBuilder for each installed dynamic feature module. This integrates the module's screens into the navigation graph. ```kotlin // Inside :app module val ALL_DYNAMIC_MODULES_MAP = listOf( ExampleModule, // ... ).associateBy { it.moduleName } NavDisplay( // ... entryProvider = entryProvider { // ... dynamicFeatureManager.installedModules .mapNotNull { ALL_DYNAMIC_MODULES_MAP[it] } .forEach { buildDynamicEntries(it) } } ) ``` -------------------------------- ### Build Synthetic Back Stack for Deep Links Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Iterate up the parent keys of a deep link key to construct a synthetic back stack. This is crucial when an Activity is started in a new task to support Up and Back navigation. ```kotlin fun buildSyntheticBackStack( deeplinkKey: DeepLinkKey, ): List { return buildList { var node: DeepKey? = startKey while (node != null) { // ensure the parent is added to the start of the list add(0, node) val parent = if (node is DeepLinkKey) { node.parent } else null node = parent } } } ``` -------------------------------- ### Request Parent ViewModel in Child Screen Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/sharedviewmodel/README.md Request a ViewModel instance from the parent's `ViewModelStoreOwner` in the child screen. This ensures you get the same ViewModel instance used by the parent. ```kotlin val parentViewModel = viewModel( viewModelStoreOwner = LocalSharedViewModelStoreOwner.current ) ``` -------------------------------- ### Identify Task Launch Flags Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Checks if an Activity was started with the FLAG_ACTIVITY_NEW_TASK flag. ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val flags = intent.flags val isNewTask = flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0 // ... } ``` -------------------------------- ### Initialize NavBackStack with Synthetic Back Stack Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Pass the generated synthetic back stack to the NavDisplay component using rememberNavBackStack. This ensures correct navigation when starting in a new task. ```kotlin val syntheticBackStack = buildBackStack(deeplinkKey) setContent { val backStack: NavBackStack = rememberNavBackStack(*(syntheticBackStack.toTypedArray())) NavDisplay( backStack = backStack, // ... ) { // ... } } ``` -------------------------------- ### Add Deep Link Key to Back Stack in Original Task Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md When an Activity is started in the current task, simply add the deep link key to the NavBackStack to handle the back button. No synthetic stack is strictly required here. ```kotlin setContent { // val deeplinkKey = parse deep link into key... val backStack: NavBackStack = rememberNavBackStack(deeplinkKey) NavDisplay( backStack = backStack, // ... ) } ``` -------------------------------- ### Create an entryProvider Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Initialize an entryProvider at the same scope as the NavigationState. ```kotlin val entryProvider = entryProvider { } ``` -------------------------------- ### Refactor NavHost to Navigation 3 Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Compare the legacy NavHost implementation with the new Navigation 3 entryProvider structure. ```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() } } ``` -------------------------------- ### Instantiate NavigationState and Navigator Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Create instances of NavigationState and Navigator, typically with the same scope as your NavController. Ensure to replace placeholder values for startRoute and topLevelRoutes. ```kotlin val navigationState = rememberNavigationState( startRoute = , topLevelRoutes = ) val navigator = remember { Navigator(navigationState) } ``` -------------------------------- ### Configure app build.gradle.kts dependencies Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Include the necessary Navigation3 runtime and UI libraries in your app module. ```kotlin dependencies { implementation(libs.androidx.navigation3.ui) implementation(libs.androidx.navigation3.runtime) // If using the ViewModel add-on library implementation(libs.androidx.lifecycle.viewmodel.navigation3) } ``` -------------------------------- ### Replace NavHost with NavDisplay Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Use NavDisplay to show navigation entries. Specify entries using navigationState.toEntries(entryProvider) and connect NavDisplay.onBack to navigator.goBack(). Consider adding DialogSceneStrategy for dialog destinations. ```kotlin import androidx.navigation3.ui.NavDisplay NavDisplay( entries = navigationState.toEntries(entryProvider), onBack = { navigator.goBack() }, sceneStrategy = remember { DialogSceneStrategy() } ) ``` -------------------------------- ### Add Navigation 3 Core Dependencies Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Add these core Navigation 3 libraries to your project's version catalog. Ensure your compileSdk is 36 or later. ```toml [versions] nv3Core = "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 Navigation3 dependencies to version catalog Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Add the ViewModel navigation add-on to your project's version catalog if required. ```toml androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } ``` -------------------------------- ### Implement NavKey interface for routes Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Update navigation routes to implement the NavKey interface to enable state saving. ```kotlin @Serializable data object RouteA ``` ```kotlin @Serializable data object RouteA : NavKey ``` -------------------------------- ### Convert AAB to APKS for Local Testing Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Convert the built AAB into APKS format with local testing enabled using bundletool. Ensure you replace '' with the actual path to your project. ```shell java -jar bundletool.jar build-apks --local-testing --bundle /app/build/outputs/bundle/debug/app-debug.aab --output app-debug.apks --overwrite ``` -------------------------------- ### Create Task Stack Builder for Up Navigation Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Construct a TaskStackBuilder to restart the app in a new task for Up navigation. This involves creating an Intent with the parent deep link URL and setting appropriate flags. ```kotlin private fun createTaskStackBuilder( deeplinkKey: NavKey?, activity: Activity?, context: Context ): TaskStackBuilder { // The intent to restart the current activity val intent = if (activity != null) { Intent(context, activity.javaClass) } else { val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) launchIntent ?: Intent() } // Pass in the deeplink url of the parent key if (deeplinkKey != null && deeplinkKey is NavDeepLinkRecipeKey) { intent.data = deeplinkKey.deeplinkUrl.toUri() } // Ensure that the MainActivity is restarted as the root of a new Task intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) // Lastly, attach the intent to the TaskStackBuilder return TaskStackBuilder.create(context).addNextIntentWithParentStack(intent) } ``` -------------------------------- ### Build Project AAB Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Use Gradle to build the Android App Bundle (AAB) for debugging. ```shell ./gradlew :app:bundleDebug ``` -------------------------------- ### Create Persistent Back Stack Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/basicparcelable/README.md Use rememberSaveable with a SnapshotStateList to maintain navigation state across configuration changes. ```kotlin @Composable fun rememberParcelableBackStack(vararg elements: T): SnapshotStateList { return rememberSaveable { mutableStateListOf(*elements) } } ``` -------------------------------- ### Create NavigationState holder Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Define a state holder class and composable functions to manage navigation back stacks and state persistence. ```kotlin // package com.example.project 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.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberDecoratedNavEntries import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.runtime.serialization.NavKeySerializer import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer /** * 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: List get() = if (topLevelRoute == startRoute) { listOf(startRoute) } else { listOf(startRoute, topLevelRoute) } } /** * Convert NavigationState into NavEntries. */ @Composable fun NavigationState.toEntries( entryProvider: (NavKey) -> NavEntry ): List> { val decoratedEntries = backStacks.mapValues { (_, stack) -> val decorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), ) rememberDecoratedNavEntries( backStack = stack, entryDecorators = decorators, entryProvider = entryProvider ) } return stacksInUse .flatMap { decoratedEntries[it] ?: emptyList() } } ``` -------------------------------- ### Create Navigator Class Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md This class handles navigation events by updating the NavigationState. It requires an instance of NavigationState to operate. ```kotlin package com.example.project import androidx.navigation3.runtime.NavKey /** * Handles navigation events (forward and back) by updating the navigation state. */ class Navigator(val state: NavigationState){ fun navigate(route: NavKey){ if (route in state.backStacks.keys){ // This is a top level route, just switch to it. state.topLevelRoute = route } else { state.backStacks[state.topLevelRoute]?.add(route) } } fun goBack(){ val currentStack = state.backStacks[state.topLevelRoute] ?: error("Stack for ${state.topLevelRoute} not found") val currentRoute = currentStack.last() // If we're at the base of the current route, go back to the start route stack. if (currentRoute == state.topLevelRoute){ state.topLevelRoute = state.startRoute } else { currentStack.removeLastOrNull() } } } ``` -------------------------------- ### Request Standalone ViewModel in Child Screen Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/sharedviewmodel/README.md Request a ViewModel instance from the default `LocalViewModelStoreOwner` in the child screen. This will provide a new, isolated ViewModel instance. ```kotlin val standaloneViewModel = viewModel() ``` -------------------------------- ### Implement Dynamic Module Entry Builder Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Implement DynamicModuleEntryBuilder within each dynamic feature module. This builder is responsible for adding module-specific entries to the main app's entry provider. ```kotlin // Inside :example module @Suppress("unused") class ExampleEntryBuilder : DynamicModuleEntryBuilder { override fun EntryProviderScope.build() { appEntry { ExampleScreen() } } } @Composable private fun ExampleScreen() { // ... } ``` -------------------------------- ### Configure NavDisplay with Shared ViewModel Decorator Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/sharedviewmodel/README.md Configure the `NavDisplay` with `rememberSharedViewModelStoreNavEntryDecorator` to enable ViewModel sharing between navigation entries. ```kotlin entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberSharedViewModelStoreNavEntryDecorator(), ) ``` -------------------------------- ### Apache License 2.0 Source: https://github.com/android/nav3-recipes/blob/main/README.md This is the Apache License, Version 2.0, which governs the use of the software. It outlines the terms under which you may use, modify, and distribute the code. ```text 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 https://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. ``` -------------------------------- ### Define a Dynamic Module Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Define a dynamic feature module by extending the DynamicModule abstract class. This class holds the module name and the entry builder class name. ```kotlin object ExampleModule : DynamicModule( entryBuilderClassName = "com.example.module.ExampleEntryBuilder", moduleName = "example" ) { @Serializable data object Screen : AppNavKey } ``` -------------------------------- ### Proguard Rule for Dynamic Module Entry Builders Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/dynamicfeature/README.md Add this Proguard rule to ensure that the entry builder class, specified by entryBuilderClassName, is accessible when R8 minification is enabled. ```proguard -keep class * implements com.example.nav3recipes.dynamicfeature.DynamicModuleEntryBuilder { public (); } ``` -------------------------------- ### Check Route Selection Source: https://github.com/android/nav3-recipes/blob/main/docs/migration-guide.md Compare the current route with the topLevelRoute from NavigationState to determine if a route is selected, simplifying the logic compared to using NavController. ```kotlin val isSelected = key == navigationState.topLevelRoute ``` -------------------------------- ### Parse and Decode Deep Links Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Handles URI parsing, pattern matching, and decoding into a NavKey within an Activity. ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val uri: Uri? = intent.data val key: NavKey = uri?.let { // Step 2: Parse request val request = DeepLinkRequest(uri) // Step 3: Find match val match = deepLinkPatterns.firstNotNullOfOrNull { pattern -> DeepLinkMatcher(request, pattern).match() } // Step 4: Decode to NavKey match?.let { KeyDecoder(match.args).decodeSerializableValue(match.serializer) } } ?: HomeKey // Fallback to home if no match or no URI setContent { val backStack = rememberNavBackStack(key) // ... setup NavDisplay } } ``` -------------------------------- ### Define Deep Link Patterns Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Maps URL patterns to NavKey serializers for deep link resolution. ```kotlin internal val deepLinkPatterns: List> = listOf( // URL pattern with exact match: "https://www.nav3recipes.com/home" DeepLinkPattern(HomeKey.serializer(), (URL_HOME_EXACT).toUri()), // URL pattern with Path arguments: "https://www.nav3recipes.com/users/with/{filter}" DeepLinkPattern(UsersKey.serializer(), (URL_USERS_WITH_FILTER).toUri()), // URL pattern with Query arguments: "https://www.nav3recipes.com/users/search?{firstName}&{age}..." DeepLinkPattern(SearchKey.serializer(), (URL_SEARCH.toUri())), ) ``` -------------------------------- ### Define Parcelable Routes Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/basicparcelable/README.md Implement the Route marker interface with Parcelable and annotate data classes with @Parcelize to enable automatic serialization. ```kotlin sealed interface Route : Parcelable @Parcelize data object RouteA : Route @Parcelize data class RouteB(val id: String) : Route ``` -------------------------------- ### Define Parent Entry for ViewModel Sharing Source: https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/sharedviewmodel/README.md Specify the parent entry for a navigation entry using metadata to enable ViewModel sharing. This ensures the child entry can access the parent's ViewModel store. ```kotlin entry( metadata = SharedViewModelStoreNavEntryDecorator.parent( ParentScreen.toContentKey() ) ) { // ... } ``` -------------------------------- ### Define Deep Link Key Interface and Objects Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Define a DeepLinkKey interface that inherits from NavKey and includes a parent field. This is used to build synthetic back stacks for deep links. Ensure all deep link keys inherit from this interface. ```kotlin interface DeepLinkKey: NavKey { val parent: NavKey } object HomeKey: NavKey object UserListKey: DeepLinkKey { override val parent = HomeKey } class UserDetailKey(user: User): DeepLinkKey { override val parent = UserListKey } ``` -------------------------------- ### Conditionally Display Up Button in TopAppBar Source: https://github.com/android/nav3-recipes/blob/main/docs/deeplink-guide.md Only display the Up button in the TopAppBar if the current screen is not the root screen of the back stack. This prevents the Up button from exiting the application. ```kotlin TopAppBar( // ... navigationIcon = { /** * Only display Up button if not on Root screen */ if (currentKey != backStack.first()) { IconButton(onClick = { backStack.navigateUp() }) { // ... } } }, ) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.