Reputation: 2281
navigation compose version 2.4.0-alpha06
I have a Navigation Drawer using Scaffold
and part of the items are dynamically generated by ViewModel.
Example items are
where A, B, C, ... all share same Screen
called Category
, with just different arguments passed (e.g. Category/A, Category/B).
Inside my Scaffold
...
val items = viewModel.getDrawerItems()
// This gives something like
// ["Home", "Category/A", "Category/B", "Category/C", ..., "Settings"]
// where each String represents "route"
...
val backstackEntry = navController.currentBackStackEntryAsState()
val currentScreen = Screen.fromRoute(
backstackEntry.value?.destination?.route
)
Log.d("Drawer", "currentScreen: $currentScreen")
items.forEach { item ->
DrawerItem(
item = item,
isSelected = currentScreen.name == item.route,
onItemClick = {
Log.d("Drawer", "destinationRoute: ${item.route}")
navController.navigate(item.route)
scope.launch {
scaffoldState.drawerState.close()
}
}
)
}
This code works pretty well, except when I visit Home screen, I want to clear all backstack upto Home not inclusive.
I've tried adding NavOptionsBuilder
...
navController.navigate(item.route) {
popUpTo(currentScreen.name) {
inclusive = true
saveState = true
}
}
...
However, this doesn't work because currentScreen.name
will give something like Category/{title}
and popUpTo only tries to look up exact match from the backstack, so it doesn't pop anything.
Is there real compose-navigation way to solve this? or should I save the last "title" somewhere in ViewModel and use it?
This tutorial from Google has similar structure, but it just stacks screens so going back from screen A -> B -> A and clicking back will just go back to B -> A, which is not ideal behavior for me.
Thank you in advance.
Upvotes: 11
Views: 20896
Reputation: 3941
You can make an extension function to serve the popUpTo functionality at all places.
fun NavHostController.navigateWithPopUp(
toRoute: String, // route name where you want to navigate
fromRoute: String // route you want from popUpTo.
) {
this.navigate(toRoute) {
popUpTo(fromRoute) {
inclusive = true // It can be changed to false if you
// want to keep your fromRoute exclusive
}
}
}
Usage
navController.navigateWithPopUp(Screen.Home.name, Screen.Login.name)
Upvotes: 14
Reputation: 2281
Inspired by @Philip Dukhov's answer, I was able to achieve what I wanted.
...
navController.navigate(item.route) {
// keep backstack until user goes to Home
if (item.route == Screen.Home.name) {
popUpTo(item.route) {
inclusive = true
saveState = true
}
} else {
// only restoreState for non-home screens
restoreState = true
}
...
Unfortunately, if I add launchSingleTop = true
, Screen with different argument is not recomposed for some reason, but that's probably another topic.
Upvotes: 1
Reputation: 88377
When you're specifying popUpTo
you should pass same item you're navigating to in this case:
navController.navigate(item.route) {
popUpTo(item.route) {
inclusive = true
}
}
Also not sure if you need to specify saveState
in this case, it's up to you:
Whether the back stack and the state of all destinations between the current destination and the
NavOptionsBuilder.popUpTo
ID should be saved for later restoration viaNavOptionsBuilder.restoreState
or the restoreState attribute using the sameNavOptionsBuilder.popUpTo
ID (note: this matching ID is true whether inclusive is true or false).
Upvotes: 6