I'm building a tabbed interface with stacked screens using Jetpack Compose Navigation and a NavigationBar. I'm navigating to the selected tab using LaunchEffect when a NavigationBar component is clicked. However, the back button is taking me back to the previous tab, which isn't the intended behavior. I'd like the back button to navigate back to the previous Activity or a stacked composable in the same tab.
Additionally, I want to preserve the state of composables within a tab when switching to another tab, similar to Google Chat App.
Could you please provide some guidance?
val navController = rememberNavController()
var selectedTab by remember { mutableIntStateOf(0) }
Scaffold(
bottomBar = {
// set navigation bar components
NavigationBar {
NavigationTab.entries.forEachIndexed { index, item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}) {
innerPadding ->
NavHost(navController = navController, startDestination = "tab1") {
// When an item is selected on Tab1Screen, the navController navigates to the Tab1Sub screen.
composable("tab1") { Tab1Screen(navController, innerPadding) }
composable("tab1/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")
Tab1SubScreen(id, innerPadding)
}
composable("tab2") { Tab2Screen() }
composable("tab3") { Tab3Screen() }
}
}
LaunchedEffect(selectedTab) {
when (selectedTab) {
0 -> navController.navigate("tab1")
1 -> navController.navigate("tab2")
2 -> navController.navigate("tab3")
}
}
This question is answered in the docs.
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
}
You didn't ask for it, but i can't help adding a few comments on codestyle:
navController
to a screen. In fact, the docs have an extra warning for that.NavigationTab
directly.Tab1
-> Home
) but forget to update the LaunchedEffect
. It's good practice to extract such shared strings. Some will use static variables holding strings like private const val HOME_SCREEN = "homeScreen"
, others will use enums like enum class NavigationTab(val route: String)
, but the latest trend seems to use serializable objects directly, see docs