Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can Jetpack Compose Navigation remember multiple back stacks?

I'm reading about Navigation in Jetpack Compose, and found this example I don't understand.

From the docs:

By using the saveState and restoreState flags, the state and back stack of that item is correctly saved and restored as you swap between bottom navigation items.

val navController = rememberNavController()
Scaffold(
  bottomBar = {
    BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination
      items.forEach { screen ->
        BottomNavigationItem(
          icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
          label = { Text(stringResource(screen.resourceId)) },
          selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
          onClick = {
            navController.navigate(screen.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 = Screen.Profile.route, Modifier.padding(innerPadding)) {
    composable(Screen.Profile.route) { Profile(navController) }
    composable(Screen.FriendsList.route) { FriendsList(navController) }
  }
}

Specifically, I don't understand how the back stack can be saved if clicking an item in the bottom bar pops the navigation stack to the root.

I would imagine a journey like:

  1. User moves to /FriendsList/Friend(A)/Friend(B)/Friend(C)
  2. User clicks Profile button, resetting the navigation stack to /Profile
  3. User clicks FriendsList button.

Based on the explanation, I would expect the navigation stack to be re-set to /FriendsList/FriendA/FriendB/FriendC, even though the onClick listener seems to set the stack to /FriendsList?

I really don't understand how this can happen, how does the navigation controller link the route to the entire navigation sub-stack? Is item.route changing state containing the full route to /FriendsList/Friend(A)/Friend(B)/Friend(C), or is something else going on? Or do I understand the example wrong?

I suspect maybe the underlying mechanism is that FriendsList contains a nested navigation graph, since the example doesn't actually show any Friend route definitions. The state of this entire nested graph is contained somehow, i.e., something like /FriendsList{FriendA/FriendB/FriendC}, and a move to /FriendsList will unpack this navigation stack. Is that kind of how it works?

like image 582
Maarten Avatar asked Aug 05 '26 20:08

Maarten


1 Answers

The code in the example you gave has only 1 navHost and 1 navController so it is not possible to save the FriendList back stack seperated from the rest back stack. To achive this behavior you need to declare nested navHosts. Each of them with his own navController and thats it. All the rest is working automatically.

Here is an example

val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination

Scaffold(
    bottomBar = {
        BottomNavigation {
            items.forEach { screen ->
                BottomNavigationItem(
                    icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
                    label = { Text(stringResource(screen.resourceId)) },
                    selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                    onClick = {
                        navController.navigate(screen.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 = Screen.ProfileNavHost.route,
        Modifier.padding(innerPadding)
    ) {

        composable(Screen.ProfileNavHost.route) {
            val profileNavController = rememberNavController()
            NavHost(
                navController = profileNavController,
                startDestination = Screen.ProfileRoot.route
            ) {
                composable(Screen.ProfileRoot.route) {
                    ProfileRoot(navController)
                }
                composable(Screen.ProfileScreen.route) {
                    ProfileScreen(navController)
                }
            }
        }

        composable(Screen.FriendsListNavHost.route) {
            val friendsListNavController = rememberNavController()
            NavHost(
                navController = friendsListNavController,
                startDestination = Screen.FriendsListRoot.route
            ) {
                composable(Screen.FriendsListRoot.route) {
                    FriendsList(navController)
                }
                composable(Screen.FriendsListFriend.route) {
                    FriendsListFriend(navController)
                }
            }
        }
    }
}
like image 71
vasberc Avatar answered Aug 09 '26 11:08

vasberc