Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-navigationandroid-jetpack-compose-scaffold

Jetpack Compose Navigation: PopUpTo Screen from Screens that have same route except argument


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

  • Home
  • A
  • B
  • C ...
  • Settings

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.


Solution

  • 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 via NavOptionsBuilder.restoreState or the restoreState attribute using the same NavOptionsBuilder.popUpTo ID (note: this matching ID is true whether inclusive is true or false).