Jetpack Compose 导航 - 底部导航多个返回堆栈 - 查看模型范围问题

Ced*_*dar 5 android kotlin android-jetpack-navigation android-jetpack-compose jetpack-compose-navigation

所以我有两个选项卡,选项卡 A 和选项卡 B。每个选项卡都有自己的后堆栈。我使用此谷歌文档中的代码实现了多个返回堆栈导航

    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
            }
          }
        )
      }
    }
  }
) { 
  NavHost(navController, startDestination = A1.route) {
    composable(A1.route) { 
       val viewModelA1 = hiltViewModel() 
       A1(viewModelA1) 
    }
    composable(A2.route) { 
       val viewModelA2 = hiltViewModel() 
       A2(viewModelA2) 
    }
    composable(A3.route) { 
       val viewModelA3 = hiltViewModel() 
       A3(viewModelA3) 
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

选项卡 A 有 3 个屏幕(屏幕 A1 -> 屏幕 A2 -> 屏幕 A3)。我使用该函数实例化视图模型,并在每个屏幕的块hiltViewModel()内调用它composable()

问题是当我从 A1 导航到 A2 再到 A3,然后当我将选项卡更改为选项卡 B 时,屏幕 A2 的视图模型似乎正在被处置(onCleared被调用)。因此,当我返回到显示屏幕 A3 的选项卡 A,然后返回到屏幕 A2 时,A2 的视图模型将再次实例化(init再次调用块)。我想要实现的是保留此流程的 A2 视图模型,直到我退出 A2。

这可能吗?

Phi*_*hov 3

当您过快地单击下一个导航项而当前视图显示转换尚未完成时,这似乎是一个错误。这是一个已知问题,请为其加注星标以引起更多关注。

同时,您可以等待当前屏幕转换完成,然后再导航到下一个屏幕。为此,您可以检查visibleEntries变量并仅在其仅包含单个项目后进行导航。

另外,我认为当前文档没有提供底部导航的最佳示例,因为如果您不在开始目标屏幕上,那么当我希望视图被关闭时,按后退按钮会将您带回到开始目的地。因此,我也改变了您的导航方式,如果您对文档行为感到满意,您可以fun navigate()用您自己的内容替换 的内容。

val navController = rememberNavController()
var waitEndAnimationJob by remember { mutableStateOf<Job?>(null)}
Scaffold(
    bottomBar = {
        BottomNavigation {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentDestination = navBackStackEntry?.destination
            val scope = rememberCoroutineScope()
            items.forEach { screen ->
                fun navigate() {
                    navController.navigate(screen.route) {
                        val navigationRoutes = items
                            .map(Screen::route)
                        val firstBottomBarDestination = navController.backQueue
                            .firstOrNull { navigationRoutes.contains(it.destination.route) }
                            ?.destination
                        // remove all navigation items from the stack
                        // so only the currently selected screen remains in the stack
                        if (firstBottomBarDestination != null) {
                            popUpTo(firstBottomBarDestination.id) {
                                inclusive = true
                                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
                    }
                }
                BottomNavigationItem(
                    icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
                    label = { Text(stringResource(screen.resourceId)) },
                    selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                    onClick = {
                        // if we're already waiting for an other screen to start appearing
                        // we need to cancel that job   
                        waitEndAnimationJob?.cancel()
                        if (navController.visibleEntries.value.count() > 1) {
                            // if navController.visibleEntries has more than one item
                            // we need to wait animation to finish before starting next navigation
                            waitEndAnimationJob = scope.launch {
                                navController.visibleEntries
                                    .collect { visibleEntries ->
                                        if (visibleEntries.count() == 1) {
                                            navigate()
                                            waitEndAnimationJob = null
                                            cancel()
                                        }
                                    }
                            }
                        } else {
                            // otherwise we can navigate instantly
                            navigate()
                        }
                    }
                )
            }
        }
    }
) { innerPadding ->
    // ...
}
Run Code Online (Sandbox Code Playgroud)