From b7674258cf63ab5919c234030951645cecad44cf Mon Sep 17 00:00:00 2001 From: Jeremy Woods Date: Thu, 14 Aug 2025 17:30:37 +0000 Subject: [PATCH 1/4] Add nav2 to nav3 snippets markdown file Publishing snippest that show some examples of converting common patterns in nav2 to nav3. --- Nav2ToNav3Snippets.md | 1086 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1086 insertions(+) create mode 100644 Nav2ToNav3Snippets.md diff --git a/Nav2ToNav3Snippets.md b/Nav2ToNav3Snippets.md new file mode 100644 index 0000000..bc0bf6f --- /dev/null +++ b/Nav2ToNav3Snippets.md @@ -0,0 +1,1086 @@ +# Navigation 2.0 to Navigation3 - Code snippets + +This document serves as a guide to transitioning Navigation 2 snippets to Navigation 3. It aims to +provide code examples for the most commonly used Navigation 2 APIs. These examples are meant to serve +as examples and not the source of truth for how you should implement your code, which should be +tailored to your specific use case. + +These use cases are based on Compose only implementation, not Fragments. + +## Retrieving a NavController + +Navigation 2: +``` +val navController = rememberNavController() +``` + +Navigation3: +``` +val backstack = remember { mutableStateListOf(MyKey) } +``` + +## Building Your Graph + +Navigation 2: +``` +NavHost(navController, "myGraph", "profile") { + composeable("profile") { /* content */ } + composeable("friends") {...} +} +``` + +Navigation3: +``` +// Option 1: entryProvider +NavDisplay(backStack, ..., + entryProvider(fallback) { + entry("profile") { /* content */ } + entry("friends") { /* content */ } + } +) + +// Option 2: When statement +NavDisplay(backStack) { + when(it) { + "profile" -> NavEntry("profile") { /* content */ } + "friends" -> NavEntry("friends") { /* content */ } + else -> + + } +} +``` + +## Navigation to a Destination + +Navigation 2: +``` +navController.navigate(Route) +``` + +Navigation3: +``` +backstack.add(Key) +``` + +## Pop Back + +Navigation 2: +``` +navController.popBackStack() +``` + +Navigation3: +``` +backStack.removeAt(backstack.size - 1) +``` + +## Pop back to a particular destination + +Navigation 2: +``` +navController.popBackStack(Route2, false) + +// if true pop one more +``` + +Navigation3: +``` +backstack.dropLastWhile { it != Route2 } +``` + +## Handle a failed pop back + +Navigation 2: +``` +if (!navController.popBackStack()) { + // Call finish() on your Activity + finish() +} +``` + +Navigation3: +``` +// Not really a case. +// If you remove all the items from the backstack it will crash +// When there is nothing to pop it will go to the next active callback. +// To ensure that the activity finishes, you could do the following. +val handler = BackHandler(true) { + // This handler will receive system back if NavDisplay's handler is disabled + finish() +} + +if (backstack.size > 1) { + backStack.removeAt(backStack.size - 1) +} else { + finish() +} + +NavDisplay(backstack) { } +``` + +## Pop up to a destination the navigate + +Navigation 2: +``` +navController.navigate( + route = route, + navOptions = navOptions { + popUpToRoute("root") + } +) +``` + +Navigation3: +``` +backstack.dropLastWhile { it != "root" }.add(route) +``` + +## Save state when popping up + +Navigation 2: +``` +navController.navigate( + route = route, + navOptions = navOptions { + popUpToRoute("root") + saveState = true + } +) +``` + +Navigation3: +``` +val backStack1 = remember { mutableListOf(Root, Profile) } +val backStack2 = remember { mutableListOf(Root, Friends) } + +val backStack = if (condition) { + backstack1 +else { + backstack2 +} + +NavDisplay (backStack, ...) +``` + +## Defining Type Safe Routes + +Navigation 2: +``` +@Serializable +object Profile + +NavHost(...) { + composable(...) { } +} +``` + +Navigation3: +``` +@Serializable +object Profile + +NavDisplay(..., entryProvider = entryProvider { + entry { } +}) +``` + +## Navigation to Type Safe Routes + +Navigation 2: +``` +@Serializable +data class Profile(id: String = "No Profile") + +NavHost(navController,...) { + composable(...) { + val profile = it.toRoute() + val id = profile.id + // Do something with the id + } +} + +navController.navigate(Profile) +``` + +Navigation3: +``` +@Serializable +data class Profile(id: String = "No Profile") + +NavDisplay(backStack,..., entryProvider = entryProvider { + entry { profile -> + val id = profile.id + // do something with the id + } +}) + +backStack.add(Profile("Some Id")) +``` + +## Navigating to a Dialog + +Navigation 2: +``` +val navController = rememberNavController() + +NavHost(navController, startDestination = Home) { + composable { ... } + dialog { ... } +} + +navController.navigate(Dialog) +``` + +Navigation3: +``` +val backStack = remember { mutableStateListOf(Home) } + +NavDisplay(backStack, sceneStrategy = DialogSceneStrategy()) { + when(it) { + Home -> NavEntry(it, metadata = metadata = DialogSceneStrategy.dialog()) { + // content for the dialog + } + } +} +``` + +## Navigating to an Activity + +Navigation 2: +``` +val navController = rememberNavController() + +NavHost(navController, startDestination = Home) { + composable { ... } + activity { ... } +} + +navController.navigate(SomeActivity) +``` + +Navigation3: +``` +val backStack = remember { mutableStateListOf(Home) } + +NavDisplay(backStack) { + when(it) { + Home -> NavEntry(it) {...} + SomeActivity -> NavEntry(it) { + startActivity(Intent().setClass(context, SomeActivity::class) + } + } +} +``` + +## Encapsulation + +Navigation 2: +``` +@Serializable +object Home + +fun NavGraphBuilder.homeDestination() { + composable { HomeScreen( /* ... */ ) } +} + +// MyApp.kt + +@Composable +fun MyApp() { + ... + NavHost(navController, startDestination = Contacts) { + homeDestination() + } +} +``` + +Navigation3: +``` +@Serializable +object Home + +fun EntryProviderBuilder.homeDestination() { + entry { HomeScreen( /* ... */ ) } +} + +// MyApp.kt + +@Composable +fun MyApp() { + ... + NavDisplay(backStack, entryProvider = entryProvider { + homeDestination() + }) +} +``` + +## Nested Navigation + +Navigation 2: +``` +@Serializable object Title + +// Route for nested graph +@Serializable object Game + +// Routes inside nested graph +@Serializable object Match +@Serializable object InGame + + +NavHost(navController, startDestination = Title) { + composable { + TitleScreen( + onPlayClicked = { navController.navigate(route = Game) } + ) + } + navigation<Game>(startDestination = Match) { + composable<Match> { + MatchScreen( + onStartGame = { navController.navigate(route = InGame) } + ) + } + } +} +``` + +Navigation3: +``` +@Serializable object Title + +// Routes inside nested graph +@Serializable object Match + +val mainBackstack = remember { mutableStateListOf<Any>(Title) } +val gameBackstack = remember { mutableStateListOf<Any>(Match) } + +var backstack = mainBackStack + +NavDisplay(backStack, ..., + entryProvider() { + entry(Title) { + TitleScreen( + onPlayClicked = { backStack += gameBackStack } + ) + } + entry(Game) { + MatchScreen( + onStartGame = { backStack.add(InGame) } + ) + } + } +) + +``` + +## Animate between destinations + +Navigation 2: +``` +val navController = rememberNavController() + +NavHost(navController, startDestination = Home) { + composable<Home> { ... } + composable<Profile>( + enterTransition = { /* my custom transition */ } + exitTransition = { /* my custom transition */ } + ) {...} +} + +navController.navigate(Profile) +``` + +Navigation3: +``` +val backStack = rememberMutableBackStackOf<Any>(Home) + +NavDisplay(backstack) { + when(it) { + is Home -> NavEntry(it) { ... } + is Profile -> + NavEntry(it, transition(/* enterTransition */, /* exitTransition*/)) { + } + else -> + } +} + +backStack.add(Profile) +``` + +## Add sharedElements between destinations + +Navigation 2: +``` +SharedTransitionLayout { + val selectFirst = mutableStateOf(true) + NavHost(navController, startDestination = RedBox) { + composable<RedBox> { + Box( + Modifier.sharedBounds( + rememberSharedContentState("name"), + this + ) + .clickable( + onClick = { + selectFirst.value = !selectFirst.value + navController.navigate(BlueBox) + } + ) + .background(Color.Red) + .size(100.dp) + ) { + Text("start", color = Color.White) + } + } + composable<BlueBox> { + Box( + Modifier.offset(180.dp, 180.dp) + .sharedBounds( + rememberSharedContentState("name"), + this + ) + .clickable( + onClick = { + selectFirst.value = !selectFirst.value + navController.popBackStack() + } + ) + .alpha(0.5f) + .background(Color.Blue) + .size(180.dp) + ) { + Text("finish", color = Color.White) + } + } + } +} +``` + +Navigation3: +``` +val backStack = remember { mutableStateListOf<Any>(RedBox) } +SharedTransitionLayout { + NavDisplay(backStack, ..., + entryProvider() { + entry(RedBox) { + Box( + Modifier.sharedBounds( + rememberSharedContentState("name"), + LocalNavAnimatedContentScope.current + ) + .clickable( + onClick = { + selectFirst.value = !selectFirst.value + navController.navigate(BlueBox) + } + ) + .background(Color.Red) + .size(100.dp) + ) { + Text("start", color = Color.White) + } + } + entry(BlueBox) { + Box( + Modifier.offset(180.dp, 180.dp) + .sharedBounds( + rememberSharedContentState("name"), + LocalNavAnimatedContentScope.current + ) + .clickable( + onClick = { + selectFirst.value = !selectFirst.value + navController.popBackStack() + } + ) + .alpha(0.5f) + .background(Color.Blue) + .size(180.dp) + ) { + Text("finish", color = Color.White) + } + } + } + ) +} +``` + +## Apply pop animations to activity transitions + +Navigation 2: +``` +override fun finish() { + super.finish() + ActivityNavigator.applyPopAnimationsToPendingTransition(this) +} +``` + +Navigation3: +``` +override fun finish() { + super.finish() + overridePendingTransition(R.anim.popEnterAnim, R.anim.popExitAnim) +} +``` + +## Deeplinking to a destination + +Navigation 2: +``` +NavHost(...) { + composable(..., + deepLinks = listOf(navDeepLink { url = "deeplink://mydeeplink" }) + ) { + + } +} + +navController(NavDeepLinkRequest.fromUrl("deeplink://mydeeplink").build()) +``` + +Navigation3: +``` +// Do not deep link internally to destination, just go to them. + +val backStack = remember { mutableListOf(MyKey)} + +NavDisplay(...) { + when(it) { + MyKey -> NavEntry(MyKey) { } + MyKey2 -> NavEntry(MyKey2) { } + } +} + +backStack.add(MyKey2) +``` + +## Navigate with actions and mimetypes + +Navigation 2: +``` +NavHost(...) { + composable(..., + deepLinks = listOf(navDeepLink { action = "action" mimeType = "type" }) + ) { + + } +} + +navController( + NavDeepLinkRequest.fromAction("action").setMimeType("type").build() +) +``` +Navigation3: +``` +// Do not deep link internally to destination, just go to them. + +val backStack = remember { mutableListOf(MyKey)} + +NavDisplay(...) { + when(it) { + MyKey -> NavEntry(MyKey) { } + MyKey2 -> NavEntry(MyKey2) { } + } +} + +backStack.add(MyKey2) +``` + +## Handling deep link from Intent + +Navigation 2: +``` +override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + navController.handleDeepLink(intent) +} +``` + +Navigation3: +``` +val intent = Intent() +intent.data = parseBackStackToUri(backStack) + +interface Destination { + val deepLink: Uri? = null + val parent: Destination? = null +} + +data class Profile(id: String? = null) : Destination { + override val deepLink = "https://www.example.com/profile/$id" + override val parent = ProfileList +} + +object ProfileList : Destination { + override val parent = Root +} + +object Root : Destination + +// From another API where you only have the data +override fun onNewIntent(intent: Intent?) { + // super.onNewIntent(intent) + val uri = intent.data + backStack = changeUriToBackStack(uri) +} + + + +fun parseBackStackToUri(backStack: List<Any>): Uri? { + return backStack.last().deepLink +} + +fun changeUriToBackStack(uri: URI): List<Any> { + // find every instance of Destination + val match = destinationList.takeIf { + uri == it.deepLink + } + // find a back stack for the match + if (match != null) { + val backStack = mutableListOf(match) + // Add in a synthetic back stack + while (backStack.first().parent != null) { + backStack = backStack.first().parent + backStack + } + return backStack + } + return listOf() +} + +// You own the intent and pull it out +val intent = Intent() +intent.extras = encodeToSavedState(backStack) + +override fun onNewIntent(intent: Intent?) { + // super.onNewIntent(intent) + backStack = decodeFromSavedState(intent.extras!!) +} +``` + +## Conditional Navigation + +Navigation3: +``` +var isLoggedIn by remember { mutableStateOf(false) } +val backStack = remember { mutableStateListOf<Any>(Home) } +val loginStack = remember { mutableStateListOf<Any>(Login) } + +val navBackStack = + if (isLoggedIn) { + backStack + } else { + backStack + loginStack + } + +*NavDisplay( + backStack = navBackStack, + modifier = Modifier.padding(paddingValues), + onBack = { backStack.removeLastOrNull() }, + entryProvider = entryProvider() { + entry(Home) { + Column { + Text("Welcome to Nav3") + Button(onClick = { + backStack.add(Product("123")) + }) { + Text("Click to add Product") + } + } + } + entry(Product) { + Text("Product ${key.id} ") + } + entry(Login) { + Column { + Text("Login screen") + Button(onClick = { isLoggedIn = !isLoggedIn }) { + Text(if (isLoggedIn) { "Logout" } else { "Login" }) + } + Button(onClick = { backStack.add(Product("ABC"))}) { + Text("Go to product") + } + } + } + } +) +``` + +## Circular Navigation + +Navigation 2: +``` +composable("c") { + DestinationC( + onNavigateToA = { + navController.navigate("a") { + popUpTo("a") { + inclusive = true + } + } + }, + ) +} +``` + +Navigation3: +``` +entry("c") { + DestinationC( + onNavigateToA = { + backstack.dropLastWhile { it != "a"} + backstack.removeLast() + backstack.add("a") + }, + ) +} +``` + +## Reference a destination using NavBackStackEntry + +Navigation 2: +``` +val entry = navController.getBackStackEntry<Key>() + +val lifecycle = entry.lifecycle +val viewModel = viewModel(entry) +``` + +Navigation3: +``` +// Does not exist in Compose land +// You would use CompositionLocals to get the proper component + +val lifecycle = LocalLifecycleOwner.current.lifecycle +val viewModel = viewModel(LocalViewModelStoreOwner.current) +``` + +## Share UI-related data with ViewModel + +Navigation 2: +``` +@Composable +fun MyScreen(onNavigate: (Any) -> Unit) { + Button(onClick = { onNavigate(Profile) } { /* ... */ } +} + +``` + +Navigation3: +``` +@Composable +fun MyScreen(onNavigate: (Any) -> Unit) { + Button(onClick = { onNavigate(Profile) } { /* ... */ } +} +``` + +## Expose events from composable + +Navigation 2: +``` +@Composable +fun MyScreen(onNavigate: (Any) -> Unit) { + Button(onClick = { onNavigate(Profile) } { /* ... */ } +} +``` +Navigation3: +``` +@Composable +fun MyScreen(onNavigate: (Any) -> Unit) { + Button(onClick = { onNavigate(Profile) } { /* ... */ } +} +``` + +## Support multiple back stacks + +Navigation 2: +``` +val navController = rememberNavController() +Scaffold( + bottomBar = { + BottomNavigation { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + topLevelRoutes.forEach { topLevelRoute -> + BottomNavigationItem( + icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) }, + label = { Text(topLevelRoute.name) }, + selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true, + onClick = { + navController.navigate(topLevelRoute.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } +) { innerPadding -> + NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) { + composable<Profile> { ProfileScreen(...) } + composable<Friends> { FriendsScreen(...) } + } +} +``` + +Navigation3: +``` +data class TopLevelStack(val stack: MutableList<Any>, val icon: ImageVector) + +val profileStack = remember { mutableStateListOf<Any>(Profile) } +val friendsStack = remember { mutableStateListOf<Any>(Friends) } + +val TOP_LEVEL_STACKS = listOf( + TopLevelStack(stack = profileStack, icon = Icons.Profile), + TopLevelStack(stack = friendsStack, icon = Icons.Friends), +) + +var backstack = profileStack + +Scaffold( + bottomBar = { + NavigationBar { + TOP_LEVEL_STACKS.forEach { topLevelStack -> + NavigationBarItem( + selected = topLevelStack.stack.last() == backStack.last(), + onClick = { + backStack.popUntil(Profile) + if (backStack.last() != topLevelStack.stack.last()) { + backStack += topLevelStack.stack + } + }, + icon = { + Icon( + imageVector = topLevelStack.icon, + contentDescription = topLevelStack.stack.last()::class.simpleName + ) + } + ) + } + } + } +) { + NavDisplay( + backStack = backStack, + onBack = { backStack.removeAt( backStack.size - 1 ) }, + modifier = Modifier.padding(it) + entryProvider { + entry(Profile) { ProfileScreen(...) } + entry(Friends) { FriendsScreen(...) } + } + } +} +``` + +## Integration with the bottom nav bar + +Navigation 2: +``` +data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector) + + +val topLevelRoutes = listOf( + TopLevelRoute("Profile", Profile, Icons.Profile), + TopLevelRoute("Friends", Friends, Icons.Friends) +) + + +val navController = rememberNavController() +Scaffold( + bottomBar = { + BottomNavigation { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + topLevelRoutes.forEach { topLevelRoute -> + BottomNavigationItem( + icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) }, + label = { Text(topLevelRoute.name) }, + selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true, + onClick = { + navController.navigate(topLevelRoute.route) { + // Pop up to the start destination of the graph to + // avoid building up a large stack of destinations + // on the back stack as users select items + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + // Avoid multiple copies of the same destination when + // reselecting the same item + launchSingleTop = true + // Restore state when reselecting a previously selected item + restoreState = true + } + } + ) + } + } + } +) { innerPadding -> + NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) { + composable<Profile> { ProfileScreen(...) } + composable<Friends> { FriendsScreen(...) } + } +} +``` + +Navigation3: +``` +data class TopLevelRoute(val key: Any, val icon: ImageVector) + +val TOP_LEVEL_ROUTES = listOf( + TopLevelRoute(key = Profile, icon = Icons.Profile), + TopLevelRoute(key = Friends, icon = Icons.Friends), +) + +val backStack = remember { mutableStateListOf<Any>(Profile) } + +Scaffold( + bottomBar = { + NavigationBar { + TOP_LEVEL_ROUTES.forEach { topLevelRoute -> + NavigationBarItem( + selected = topLevelRoute.key == backStack.last(), + onClick = { + backStack.popUntil(Home) + if (backStack.last() != topLevelRoute.key) { + backStack.add(topLevelRoute.key) + } + }, + icon = { + Icon( + imageVector = topLevelRoute.icon, + contentDescription = topLevelRoute.key::class.simpleName + ) + } + ) + } + } + } +) { + NavDisplay( + backStack = backStack, + onBack = { backStack.removeAt( backStack.size - 1 ) }, + modifier = Modifier.padding(it) + entryProvider { + entry(Profile) { ProfileScreen(...) } + entry(Friends) { FriendsScreen(...) } + } + } +} +``` + +## Integration with the top app bar + +Navigation 2: +``` +// No guidance for this in Nav 2 +``` + +Navigation3: +``` +val TOP_LEVEL_ROUTES = listOf( + TopLevelRoute(key = Profile, icon = Icons.Profile), + TopLevelRoute(key = Friends, icon = Icons.Friends), +) + +val backStack = remember { mutableStateListOf<Any>(Profile) } + +Scaffold( + topBar = { + TopAppBar( + title = { + Text( + backstack.last { TOP_LEVEL_ROUTES.contains(it) }::class.simpleName + ) + } + ) + } +) { + *NavDisplay( + backStack = backStack, + onBack = { backStack.removeAt( backStack.size - 1 ) }, + modifier = Modifier.padding(it) + entryProvider { + entry(Profile) { ProfileScreen(...) } + entry(Friends) { FriendsScreen(...) } + } + } +} +``` + +## Testing + +Navigation 2: +``` +class NavigationTest { + + @get:Rule + val composeTestRule = createComposeRule() + lateinit var navController: TestNavHostController + + @Before + fun setupAppNavHost() { + composeTestRule.setContent { + navController = TestNavHostController(LocalContext.current) + navController.navigatorProvider.addNavigator(ComposeNavigator()) + AppNavHost(navController = navController) + } + } + + // Unit test + @Test + fun appNavHost_verifyStartDestination() { + composeTestRule + .onNodeWithContentDescription("Start Screen") + .assertIsDisplayed() + } +} +``` + +Navigation3: +``` +class NavigationTest { + + @get:Rule + val composeTestRule = createComposeRule() + lateinit var backStack: MutableList<Any> + val entryProvider = entryProvider() { ... } + + @Before + fun setupAppNavHost() { + composeTestRule.setContent { + backStack = rememberMutableStateListOf(Start) + TestDisplay(backStack = backStack, entryProvider = entryProvider) + } + } + + // Unit test + @Test + fun testDisplay_verifyStartDestination() { + composeTestRule + .onNodeWithContentDescription("Start Screen") + .assertIsDisplayed() + } +} +``` + +## Interoperability + +Navigation 2: +``` +NavHost(navController, Graph, Profile) { + composeable(Profile) { AndroidFragment<ProfileFragment>() } +} +``` + +Navigation3: +``` +NavDisplay(backStack, ..., + entryProvider() { + entry(Profile) { AndroidFragment<ProfileFragment>() } + } +) +``` \ No newline at end of file From fe6d0d69d1580ac15b8c0119d9330eca428162da Mon Sep 17 00:00:00 2001 From: Jeremy Woods <jbwoods@google.com> Date: Mon, 18 Aug 2025 20:40:26 +0000 Subject: [PATCH 2/4] Fix typos and update backstack logic in Nav3 snippets --- Nav2ToNav3Snippets.md | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Nav2ToNav3Snippets.md b/Nav2ToNav3Snippets.md index bc0bf6f..d79f9ca 100644 --- a/Nav2ToNav3Snippets.md +++ b/Nav2ToNav3Snippets.md @@ -24,8 +24,8 @@ val backstack = remember { mutableStateListOf<Any>(MyKey) } Navigation 2: ``` NavHost(navController, "myGraph", "profile") { - composeable("profile") { /* content */ } - composeable("friends") {...} + composable("profile") { /* content */ } + composable("friends") {...} } ``` @@ -85,7 +85,10 @@ navController.popBackStack(Route2, false) Navigation3: ``` -backstack.dropLastWhile { it != Route2 } +val index = backstack.lastIndexOf(Route2) +if (index != -1) { + backstack.removeRange(index + 1, backstack.size) +} ``` ## Handle a failed pop back @@ -118,7 +121,7 @@ if (backstack.size > 1) { NavDisplay(backstack) { } ``` -## Pop up to a destination the navigate +## Pop up to a destination then navigate Navigation 2: ``` @@ -132,7 +135,11 @@ navController.navigate( Navigation3: ``` -backstack.dropLastWhile { it != "root" }.add(route) +val index = backstack.lastIndexOf("root") +if (index != -1) { + backstack.removeRange(index + 1, backstack.size) +} +backstack.add(route) ``` ## Save state when popping up @@ -237,7 +244,7 @@ val backStack = remember { mutableStateListOf<Any>(Home) } NavDisplay(backStack, sceneStrategy = DialogSceneStrategy<Any>()) { when(it) { - Home -> NavEntry(it, metadata = metadata = DialogSceneStrategy.dialog()) { + Home -> NavEntry(it, metadata = DialogSceneStrategy.dialog()) { // content for the dialog } } @@ -471,7 +478,7 @@ SharedTransitionLayout { .clickable( onClick = { selectFirst.value = !selectFirst.value - navController.navigate(BlueBox) + backstack.add(BlueBox) } ) .background(Color.Red) @@ -490,7 +497,7 @@ SharedTransitionLayout { .clickable( onClick = { selectFirst.value = !selectFirst.value - navController.popBackStack() + backstack.removeLast() } ) .alpha(0.5f) @@ -560,7 +567,12 @@ Navigation 2: ``` NavHost(...) { composable(..., - deepLinks = listOf(navDeepLink { action = "action" mimeType = "type" }) + deepLinks = listOf( + navDeepLink { + action = "action" + mimeType = "type" + } + ) ) { } @@ -672,7 +684,7 @@ val navBackStack = backStack + loginStack } -*NavDisplay( +NavDisplay( backStack = navBackStack, modifier = Modifier.padding(paddingValues), onBack = { backStack.removeLastOrNull() }, @@ -727,8 +739,11 @@ Navigation3: entry("c") { DestinationC( onNavigateToA = { - backstack.dropLastWhile { it != "a"} - backstack.removeLast() + val index = backstack.lastIndexOf("a") + if (index != -1) { + // Pop up to and including "a" + backstack.removeRange(index, backstack.size) + } backstack.add("a") }, ) @@ -1072,7 +1087,7 @@ class NavigationTest { Navigation 2: ``` NavHost(navController, Graph, Profile) { - composeable(Profile) { AndroidFragment<ProfileFragment>() } + composable(Profile) { AndroidFragment<ProfileFragment>() } } ``` From 791f5dcf813d3adcc902fb5529aa7532cd607ae8 Mon Sep 17 00:00:00 2001 From: jbw0033 <jbw0033@auburn.edu> Date: Tue, 19 Aug 2025 18:32:39 +0000 Subject: [PATCH 3/4] Fix errors in snippets --- Nav2ToNav3Snippets.md | 105 +++++++++++------------------------------- 1 file changed, 27 insertions(+), 78 deletions(-) diff --git a/Nav2ToNav3Snippets.md b/Nav2ToNav3Snippets.md index d79f9ca..fea5354 100644 --- a/Nav2ToNav3Snippets.md +++ b/Nav2ToNav3Snippets.md @@ -33,7 +33,7 @@ Navigation3: ``` // Option 1: entryProvider NavDisplay(backStack, ..., - entryProvider(fallback) { + entryProvider = entryProvider(fallback) { entry("profile") { /* content */ } entry("friends") { /* content */ } } @@ -44,7 +44,7 @@ NavDisplay(backStack) { when(it) { "profile" -> NavEntry("profile") { /* content */ } "friends" -> NavEntry("friends") { /* content */ } - else -> + else -> error("Unknown route: $it") } } @@ -273,7 +273,8 @@ NavDisplay(backStack) { when(it) { Home -> NavEntry(it) {...} SomeActivity -> NavEntry(it) { - startActivity(Intent().setClass(context, SomeActivity::class) + val context = LocalContext.current + context.startActivity(Intent(context, SomeActivity::class.java)) } } } @@ -357,6 +358,7 @@ Navigation3: // Routes inside nested graph @Serializable object Match +@Serializable object InGame val mainBackstack = remember { mutableStateListOf<Any>(Title) } val gameBackstack = remember { mutableStateListOf<Any>(Match) } @@ -364,13 +366,13 @@ val gameBackstack = remember { mutableStateListOf<Any>(Match) } var backstack = mainBackStack NavDisplay(backStack, ..., - entryProvider() { + entryProvider = entryProvider { entry(Title) { TitleScreen( onPlayClicked = { backStack += gameBackStack } ) } - entry(Game) { + entry(Match) { MatchScreen( onStartGame = { backStack.add(InGame) } ) @@ -468,7 +470,7 @@ Navigation3: val backStack = remember { mutableStateListOf<Any>(RedBox) } SharedTransitionLayout { NavDisplay(backStack, ..., - entryProvider() { + entryProvider = entryProvider { entry(RedBox) { Box( Modifier.sharedBounds( @@ -542,7 +544,7 @@ NavHost(...) { } } -navController(NavDeepLinkRequest.fromUrl("deeplink://mydeeplink").build()) +navController.navigate(NavDeepLinkRequest.fromUrl("deeplink://mydeeplink").build()) ``` Navigation3: @@ -578,7 +580,7 @@ NavHost(...) { } } -navController( +navController.navigate( NavDeepLinkRequest.fromAction("action").setMimeType("type").build() ) ``` @@ -610,63 +612,7 @@ override fun onNewIntent(intent: Intent?) { Navigation3: ``` -val intent = Intent() -intent.data = parseBackStackToUri(backStack) - -interface Destination { - val deepLink: Uri? = null - val parent: Destination? = null -} - -data class Profile(id: String? = null) : Destination { - override val deepLink = "https://www.example.com/profile/$id" - override val parent = ProfileList -} - -object ProfileList : Destination { - override val parent = Root -} - -object Root : Destination - -// From another API where you only have the data -override fun onNewIntent(intent: Intent?) { - // super.onNewIntent(intent) - val uri = intent.data - backStack = changeUriToBackStack(uri) -} - - - -fun parseBackStackToUri(backStack: List<Any>): Uri? { - return backStack.last().deepLink -} - -fun changeUriToBackStack(uri: URI): List<Any> { - // find every instance of Destination - val match = destinationList.takeIf { - uri == it.deepLink - } - // find a back stack for the match - if (match != null) { - val backStack = mutableListOf(match) - // Add in a synthetic back stack - while (backStack.first().parent != null) { - backStack = backStack.first().parent + backStack - } - return backStack - } - return listOf() -} - -// You own the intent and pull it out -val intent = Intent() -intent.extras = encodeToSavedState(backStack) - -override fun onNewIntent(intent: Intent?) { - // super.onNewIntent(intent) - backStack = decodeFromSavedState(intent.extras!!) -} +see deep link recipe ``` ## Conditional Navigation @@ -794,14 +740,14 @@ Navigation 2: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { - Button(onClick = { onNavigate(Profile) } { /* ... */ } + Button(onClick = { onNavigate(Profile) }) { /* ... */ } } ``` Navigation3: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { - Button(onClick = { onNavigate(Profile) } { /* ... */ } + Button(onClick = { onNavigate(Profile) }) { /* ... */ } } ``` @@ -881,8 +827,8 @@ Scaffold( NavDisplay( backStack = backStack, onBack = { backStack.removeAt( backStack.size - 1 ) }, - modifier = Modifier.padding(it) - entryProvider { + modifier = Modifier.padding(it), + entryProvider = entryProvider { entry(Profile) { ProfileScreen(...) } entry(Friends) { FriendsScreen(...) } } @@ -959,7 +905,10 @@ Scaffold( NavigationBarItem( selected = topLevelRoute.key == backStack.last(), onClick = { - backStack.popUntil(Home) + val index = backStack.lastIndexOf(Profile) + if (index != -1) { + backStack.removeRange(index, backStack.size) + } if (backStack.last() != topLevelRoute.key) { backStack.add(topLevelRoute.key) } @@ -978,8 +927,8 @@ Scaffold( NavDisplay( backStack = backStack, onBack = { backStack.removeAt( backStack.size - 1 ) }, - modifier = Modifier.padding(it) - entryProvider { + modifier = Modifier.padding(it), + entryProvider = entryProvider { entry(Profile) { ProfileScreen(...) } entry(Friends) { FriendsScreen(...) } } @@ -1008,17 +957,17 @@ Scaffold( TopAppBar( title = { Text( - backstack.last { TOP_LEVEL_ROUTES.contains(it) }::class.simpleName + backstack.last { key -> TOP_LEVEL_ROUTES.any { it.key == key } }::class.simpleName ) } ) } ) { - *NavDisplay( + NavDisplay( backStack = backStack, onBack = { backStack.removeAt( backStack.size - 1 ) }, - modifier = Modifier.padding(it) - entryProvider { + modifier = Modifier.padding(it), + entryProvider = entryProvider { entry(Profile) { ProfileScreen(...) } entry(Friends) { FriendsScreen(...) } } @@ -1093,8 +1042,8 @@ NavHost(navController, Graph, Profile) { Navigation3: ``` -NavDisplay(backStack, ..., - entryProvider() { +NavDisplay(backStack, ...,, + entryProvider = entryProvider { { entry(Profile) { AndroidFragment<ProfileFragment>() } } ) From c7cd3287e30ce6547f4ce5b38486417d687cc032 Mon Sep 17 00:00:00 2001 From: Don Turner <donturner@google.com> Date: Thu, 21 Aug 2025 10:43:00 +0100 Subject: [PATCH 4/4] Edits to code and copy, plus added a bunch of TODOs --- Nav2ToNav3Snippets.md | 233 +++++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 104 deletions(-) diff --git a/Nav2ToNav3Snippets.md b/Nav2ToNav3Snippets.md index fea5354..145f6dd 100644 --- a/Nav2ToNav3Snippets.md +++ b/Nav2ToNav3Snippets.md @@ -1,27 +1,27 @@ -# Navigation 2.0 to Navigation3 - Code snippets +# Navigation Compose (Nav2) to Navigation3 - Code snippets -This document serves as a guide to transitioning Navigation 2 snippets to Navigation 3. It aims to -provide code examples for the most commonly used Navigation 2 APIs. These examples are meant to serve -as examples and not the source of truth for how you should implement your code, which should be -tailored to your specific use case. +This is a guide for migrating common use cases in Navigation Compose (Nav2) to Navigation3. +It provides code examples for common use cases implemented using Nav2 APIs and equivalent +code examples for Nav3. These examples are not necessarily the definitive way you should +implement your code and should be carefully considered based on your individual requirements. -These use cases are based on Compose only implementation, not Fragments. +These use cases are based on Compose, not Fragments. -## Retrieving a NavController +## Retrieve a NavController -Navigation 2: +Nav2: ``` val navController = rememberNavController() ``` -Navigation3: +Nav3: ``` -val backstack = remember { mutableStateListOf<Any>(MyKey) } +val backStack = remember { mutableStateListOf<Any>(MyKey) } ``` -## Building Your Graph +## Build your navigation graph -Navigation 2: +Nav2: ``` NavHost(navController, "myGraph", "profile") { composable("profile") { /* content */ } @@ -29,9 +29,9 @@ NavHost(navController, "myGraph", "profile") { } ``` -Navigation3: +Nav3: ``` -// Option 1: entryProvider +// Option 1: Use the entryProvider DSL NavDisplay(backStack, ..., entryProvider = entryProvider(fallback) { entry("profile") { /* content */ } @@ -39,7 +39,7 @@ NavDisplay(backStack, ..., } ) -// Option 2: When statement +// Option 2: Use a when statement NavDisplay(backStack) { when(it) { "profile" -> NavEntry("profile") { /* content */ } @@ -50,40 +50,40 @@ NavDisplay(backStack) { } ``` -## Navigation to a Destination +## Navigate to a destination -Navigation 2: +Nav2: ``` navController.navigate(Route) ``` -Navigation3: +Nav3: ``` -backstack.add(Key) +backStack.add(Key) ``` -## Pop Back +## Navigate back -Navigation 2: +Nav2: ``` navController.popBackStack() ``` -Navigation3: +Nav3: ``` backStack.removeAt(backstack.size - 1) ``` -## Pop back to a particular destination +## Navigate back to a particular destination -Navigation 2: +Nav2: ``` navController.popBackStack(Route2, false) // if true pop one more ``` -Navigation3: +Nav3: ``` val index = backstack.lastIndexOf(Route2) if (index != -1) { @@ -93,7 +93,7 @@ if (index != -1) { ## Handle a failed pop back -Navigation 2: +Nav2: ``` if (!navController.popBackStack()) { // Call finish() on your Activity @@ -101,10 +101,11 @@ if (!navController.popBackStack()) { } ``` -Navigation3: +Nav3: ``` +// TODO: Explain this better // Not really a case. -// If you remove all the items from the backstack it will crash +// If you remove all the items from the back stack `NavDisplay` will crash // When there is nothing to pop it will go to the next active callback. // To ensure that the activity finishes, you could do the following. val handler = BackHandler(true) { @@ -123,7 +124,7 @@ NavDisplay(backstack) { } ## Pop up to a destination then navigate -Navigation 2: +Nav2: ``` navController.navigate( route = route, @@ -133,7 +134,7 @@ navController.navigate( ) ``` -Navigation3: +Nav3: ``` val index = backstack.lastIndexOf("root") if (index != -1) { @@ -144,7 +145,7 @@ backstack.add(route) ## Save state when popping up -Navigation 2: +Nav2: ``` navController.navigate( route = route, @@ -155,7 +156,7 @@ navController.navigate( ) ``` -Navigation3: +Nav3: ``` val backStack1 = remember { mutableListOf(Root, Profile) } val backStack2 = remember { mutableListOf(Root, Friends) } @@ -169,31 +170,31 @@ else { NavDisplay (backStack, ...) ``` -## Defining Type Safe Routes +## Define type safe routes -Navigation 2: +Nav2: ``` @Serializable -object Profile +data object Profile NavHost(...) { composable<Profile>(...) { } } ``` -Navigation3: +Nav3: ``` @Serializable -object Profile +data object Profile NavDisplay(..., entryProvider = entryProvider { entry<Profile> { } }) ``` -## Navigation to Type Safe Routes +## Reading and writing navigation arguments with type safe routes -Navigation 2: +Nav2: ``` @Serializable data class Profile(id: String = "No Profile") @@ -206,10 +207,10 @@ NavHost(navController,...) { } } -navController.navigate(Profile) +navController.navigate(Profile("Some Id")) ``` -Navigation3: +Nav3: ``` @Serializable data class Profile(id: String = "No Profile") @@ -224,9 +225,9 @@ NavDisplay(backStack,..., entryProvider = entryProvider { backStack.add(Profile("Some Id")) ``` -## Navigating to a Dialog +## Navigating to a dialog -Navigation 2: +Nav2: ``` val navController = rememberNavController() @@ -238,13 +239,14 @@ NavHost(navController, startDestination = Home) { navController.navigate(Dialog) ``` -Navigation3: +Nav3: ``` val backStack = remember { mutableStateListOf<Any>(Home) } NavDisplay(backStack, sceneStrategy = DialogSceneStrategy<Any>()) { when(it) { - Home -> NavEntry(it, metadata = DialogSceneStrategy.dialog()) { + Home -> { ... } + Dialog -> NavEntry(it, metadata = DialogSceneStrategy.dialog()) { // content for the dialog } } @@ -253,7 +255,7 @@ NavDisplay(backStack, sceneStrategy = DialogSceneStrategy<Any>()) { ## Navigating to an Activity -Navigation 2: +Nav2: ``` val navController = rememberNavController() @@ -265,7 +267,7 @@ NavHost(navController, startDestination = Home) { navController.navigate(SomeActivity) ``` -Navigation3: +Nav3: ``` val backStack = remember { mutableStateListOf<Any>(Home) } @@ -278,14 +280,16 @@ NavDisplay(backStack) { } } } + +backStack.add(SomeActivity) ``` ## Encapsulation -Navigation 2: +Nav2: ``` @Serializable -object Home +data object Home fun NavGraphBuilder.homeDestination() { composable<Home> { HomeScreen( /* ... */ ) } @@ -302,12 +306,12 @@ fun MyApp() { } ``` -Navigation3: +Nav3: ``` @Serializable object Home -fun EntryProviderBuilder.homeDestination() { +fun EntryProviderBuilder<Any>.homeDestination() { entry<Home> { HomeScreen( /* ... */ ) } } @@ -324,16 +328,16 @@ fun MyApp() { ## Nested Navigation -Navigation 2: +Nav2: ``` -@Serializable object Title +@Serializable data object Title // Route for nested graph -@Serializable object Game +@Serializable data object Game // Routes inside nested graph -@Serializable object Match -@Serializable object InGame +@Serializable data object Match +@Serializable data object InGame NavHost(navController, startDestination = Title) { @@ -352,13 +356,13 @@ NavHost(navController, startDestination = Title) { } ``` -Navigation3: +Nav3: ``` -@Serializable object Title +@Serializable data object Title // Routes inside nested graph -@Serializable object Match -@Serializable object InGame +@Serializable data object Match +@Serializable data object InGame val mainBackstack = remember { mutableStateListOf<Any>(Title) } val gameBackstack = remember { mutableStateListOf<Any>(Match) } @@ -367,12 +371,12 @@ var backstack = mainBackStack NavDisplay(backStack, ..., entryProvider = entryProvider { - entry(Title) { + entry<Title> { TitleScreen( onPlayClicked = { backStack += gameBackStack } ) } - entry(Match) { + entry<Match> { MatchScreen( onStartGame = { backStack.add(InGame) } ) @@ -382,9 +386,9 @@ NavDisplay(backStack, ..., ``` -## Animate between destinations +## Create a custom animation when navigating to and from a specific destination -Navigation 2: +Nav2: ``` val navController = rememberNavController() @@ -399,7 +403,7 @@ NavHost(navController, startDestination = Home) { navController.navigate(Profile) ``` -Navigation3: +Nav3: ``` val backStack = rememberMutableBackStackOf<Any>(Home) @@ -416,9 +420,9 @@ NavDisplay(backstack) { backStack.add(Profile) ``` -## Add sharedElements between destinations +## Add shared elements between destinations -Navigation 2: +Nav2: ``` SharedTransitionLayout { val selectFirst = mutableStateOf(true) @@ -465,13 +469,13 @@ SharedTransitionLayout { } ``` -Navigation3: +Nav3: ``` val backStack = remember { mutableStateListOf<Any>(RedBox) } SharedTransitionLayout { NavDisplay(backStack, ..., entryProvider = entryProvider { - entry(RedBox) { + entry<RedBox> { Box( Modifier.sharedBounds( rememberSharedContentState("name"), @@ -489,7 +493,7 @@ SharedTransitionLayout { Text("start", color = Color.White) } } - entry(BlueBox) { + entry<BlueBox> { Box( Modifier.offset(180.dp, 180.dp) .sharedBounds( @@ -516,7 +520,7 @@ SharedTransitionLayout { ## Apply pop animations to activity transitions -Navigation 2: +Nav2: ``` override fun finish() { super.finish() @@ -524,7 +528,7 @@ override fun finish() { } ``` -Navigation3: +Nav3: ``` override fun finish() { super.finish() @@ -532,9 +536,9 @@ override fun finish() { } ``` -## Deeplinking to a destination +## Deeplink to a destination -Navigation 2: +Nav2: ``` NavHost(...) { composable(..., @@ -547,8 +551,9 @@ NavHost(...) { navController.navigate(NavDeepLinkRequest.fromUrl("deeplink://mydeeplink").build()) ``` -Navigation3: +Nav3: ``` +// TODO: This isn't really equivalent functionality // Do not deep link internally to destination, just go to them. val backStack = remember { mutableListOf(MyKey)} @@ -565,7 +570,7 @@ backStack.add(MyKey2) ## Navigate with actions and mimetypes -Navigation 2: +Nav2: ``` NavHost(...) { composable(..., @@ -584,8 +589,9 @@ navController.navigate( NavDeepLinkRequest.fromAction("action").setMimeType("type").build() ) ``` -Navigation3: +Nav3: ``` +// TODO: This isn't really equivalent functionality // Do not deep link internally to destination, just go to them. val backStack = remember { mutableListOf(MyKey)} @@ -602,7 +608,7 @@ backStack.add(MyKey2) ## Handling deep link from Intent -Navigation 2: +Nav2: ``` override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) @@ -610,14 +616,16 @@ override fun onNewIntent(intent: Intent?) { } ``` -Navigation3: +Nav3: ``` see deep link recipe ``` -## Conditional Navigation +## Conditional navigation -Navigation3: +// TODO: No Nav2 code and there's already a recipe for this + +Nav3: ``` var isLoggedIn by remember { mutableStateOf(false) } val backStack = remember { mutableStateListOf<Any>(Home) } @@ -663,9 +671,12 @@ NavDisplay( ) ``` -## Circular Navigation +## Circular navigation + +// TODO: Is it assumed that "a" is already on the stack, that's not immediately clear from the code +// Also, not entirely clear what "circular" means in this context, is it just avoiding duplicate entries on the stack? -Navigation 2: +Nav2: ``` composable("c") { DestinationC( @@ -680,7 +691,7 @@ composable("c") { } ``` -Navigation3: +Nav3: ``` entry("c") { DestinationC( @@ -698,7 +709,9 @@ entry("c") { ## Reference a destination using NavBackStackEntry -Navigation 2: +// TODO: Not sure what this is showing me + +Nav2: ``` val entry = navController.getBackStackEntry<Key>() @@ -706,7 +719,7 @@ val lifecycle = entry.lifecycle val viewModel = viewModel(entry) ``` -Navigation3: +Nav3: ``` // Does not exist in Compose land // You would use CompositionLocals to get the proper component @@ -717,7 +730,9 @@ val viewModel = viewModel(LocalViewModelStoreOwner.current) ## Share UI-related data with ViewModel -Navigation 2: +// TODO: Code snippets for Nav2 and Nav3 are identical + +Nav2: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { @@ -726,7 +741,7 @@ fun MyScreen(onNavigate: (Any) -> Unit) { ``` -Navigation3: +Nav3: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { @@ -736,14 +751,16 @@ fun MyScreen(onNavigate: (Any) -> Unit) { ## Expose events from composable -Navigation 2: +// TODO: Code snippets for Nav2 and Nav3 are identical + +Nav2: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { Button(onClick = { onNavigate(Profile) }) { /* ... */ } } ``` -Navigation3: +Nav3: ``` @Composable fun MyScreen(onNavigate: (Any) -> Unit) { @@ -753,7 +770,9 @@ fun MyScreen(onNavigate: (Any) -> Unit) { ## Support multiple back stacks -Navigation 2: +// TODO: This seems to be the Common Navigation UI recipe, remove? + +Nav2: ``` val navController = rememberNavController() Scaffold( @@ -787,7 +806,7 @@ Scaffold( } ``` -Navigation3: +Nav3: ``` data class TopLevelStack(val stack: MutableList<Any>, val icon: ImageVector) @@ -838,7 +857,9 @@ Scaffold( ## Integration with the bottom nav bar -Navigation 2: +// TODO: There's a lot of code here, the main thing to isolate/highlight is the changes to the logic for determining whether the BottomNavigationItem is selected + +Nav2: ``` data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector) @@ -887,7 +908,7 @@ Scaffold( } ``` -Navigation3: +Nav3: ``` data class TopLevelRoute(val key: Any, val icon: ImageVector) @@ -938,12 +959,12 @@ Scaffold( ## Integration with the top app bar -Navigation 2: +Nav2: ``` // No guidance for this in Nav 2 ``` -Navigation3: +Nav3: ``` val TOP_LEVEL_ROUTES = listOf( TopLevelRoute(key = Profile, icon = Icons.Profile), @@ -957,7 +978,7 @@ Scaffold( TopAppBar( title = { Text( - backstack.last { key -> TOP_LEVEL_ROUTES.any { it.key == key } }::class.simpleName + backStack.last { key -> TOP_LEVEL_ROUTES.any { it.key == key } }::class.simpleName ) } ) @@ -977,7 +998,11 @@ Scaffold( ## Testing -Navigation 2: +// TODO: This would be better as a separate guide explaining how by separating your back stack from Nav3 (e.g. into a `Navigator` class) you can +// move some of your instrumented navigation tests into unit tests that just verify the back stack state after various +// navigation events (e.g. `NavigatorTest`) + +Nav2: ``` class NavigationTest { @@ -1004,7 +1029,7 @@ class NavigationTest { } ``` -Navigation3: +Nav3: ``` class NavigationTest { @@ -1017,7 +1042,7 @@ class NavigationTest { fun setupAppNavHost() { composeTestRule.setContent { backStack = rememberMutableStateListOf(Start) - TestDisplay(backStack = backStack, entryProvider = entryProvider) + NavDisplay(backStack = backStack, entryProvider = entryProvider) } } @@ -1033,17 +1058,17 @@ class NavigationTest { ## Interoperability -Navigation 2: +Nav2: ``` NavHost(navController, Graph, Profile) { composable(Profile) { AndroidFragment<ProfileFragment>() } } ``` -Navigation3: +Nav3: ``` -NavDisplay(backStack, ...,, - entryProvider = entryProvider { { +NavDisplay(backStack, ..., + entryProvider = entryProvider { entry(Profile) { AndroidFragment<ProfileFragment>() } } )