Jetpack Compose Navigation: PopUpTo Screen из экранов, которые имеют тот же маршрут, кроме аргумента

навигация составить версию 2.4.0-alpha06

У меня есть панель навигации, и часть элементов динамически генерируется ViewModel.

Примеры элементов:

  • Дом
  • А
  • B
  • С ...
  • Настройки

где A, B, C, ... все имеют одинаковые Screen называется Category, только с другими переданными аргументами (например, Категория / A, Категория / B).

Внутри моего 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()
            }
        }
    )
}

Этот код работает довольно хорошо, за исключением того, что когда я захожу на главный экран, я хочу очистить весь бэкстек до главного экрана, за исключением.

Я пробовал добавить NavOptionsBuilder

      ...

navController.navigate(item.route) {
    popUpTo(currentScreen.name) {
        inclusive = true
        saveState = true
    }
}
...

Однако это не работает, потому что currentScreen.name даст что-то вроде Category/{title} и popUpTo пытается только найти точное совпадение из backstack, поэтому ничего не выскакивает.

Есть ли реальный способ компоновки-навигации для решения этой проблемы? или я должен сохранить последний "заголовок" где-нибудь во ViewModel и использовать его?

Этот учебник от Google имеет аналогичную структуру, но он просто складывает экраны, поэтому, возвращаясь с экрана A -> B -> A и щелкая назад, вы просто вернетесь к B -> A, что не является идеальным поведением для меня.

Заранее спасибо.

3 ответа

Решение

Когда вы указываете popUpTo в этом случае вы должны передать тот же элемент, к которому вы переходите:

      navController.navigate(item.route) {
    popUpTo(item.route) {
        inclusive = true
    }
}

Также не уверен, нужно ли вам указывать saveState в этом случае решать вам:

Следует ли сохранять задний стек и состояние всех пунктов назначения между текущим пунктом назначения и идентификатором для последующего восстановления с помощью NavOptionsBuilder.restoreState или атрибут restoreState, используя тот же NavOptionsBuilder.popUpTo ID (примечание: этот совпадающий ID верен независимо от того, истинно ли включено или ложно).

Вы можете сделать функцию расширения, чтобы обслуживать функциональность popUpTo во всех местах.

      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
        }
    }
}

Применение

      navController.navigateWithPopUp(Screen.Home.name, Screen.Login.name)

Вдохновленный @Philip Духова в ответ , я смог достичь того, чего я хотел.

      ...

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
    }
...

К сожалению, если я добавлю launchSingleTop = true, Скрин с другим аргументом почему-то не перекомпонован, но это наверное уже другая тема.