简体   繁体   中英

Jetpack Compose & Navigation: Problems share ViewModel in nested graph

According to this example I implemented shared viewModels in a nested navigation graph.

Setup

Nested Graph:

private fun NavGraphBuilder.accountGraph(navController: NavHostController) {
   navigation(
      startDestination = "main",
      route = "account") {

       composable("main") {
          val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
          //... ui ...
       }

       composable("login") {
          val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
          //... ui ...
       }
   }
}

NavHost:

@Composable
private fun NavHost(navController: NavHostController, modifier: Modifier = Modifier){
   NavHost(
      navController = navController,
      startDestination = MainScreen.Home.route,
      modifier = modifier
   ) {
      composable("home") { HomeScreen(hiltViewModel()) }
      composable("otherRoute") { OtherScreen(hiltViewModel()) }
      accountGraph(navController)
   }
}

BottomNavBar:

@Composable
private fun ButtonNav(navController: NavHostController) {
   BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination

      items.forEach { screen ->
         BottomNavigationItem(
            icon = { ... },
            label = { ... },
            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
                  navController.graph.startDestinationRoute?.let { route ->
                     popUpTo(route) { saveState = true }
                  }

                  // Avoid multiple copies of the same destination when
                  // re-selecting the same item
                  launchSingleTop = true

                  // Restore state when re-selecting a previously selected item
                  restoreState = true
               }
            }
         )
      }
   }
}

Problem

With this setup if I naviagte to "account" (the nested graph) and back to any other route I get the error:

java.lang.IllegalArgumentException: No destination with route account is on the NavController's back stack. The current destination is Destination(0x78dd8526) route=otherRoute

Assumptions / Research Results

BottomNavItem

The exception did not occure when I remove the popUpTo(route) onClick. But then I ended up with a large stack.

lifecycle of backStackEntry

Have a look at the following:

//...
composable("main") { backStackEntry ->
   val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
   //... ui ...
}
//...

I found out when navigating back the composable which will be left will be recomposed but in this case the backStackEntry seams to have another lifecycle.currentState because if I wrap the whole composable like this:

//...
composable("main") { backStackEntry ->
  if(backStackEntry.lifecycle.currentState == Lifecycle.State.RESUMED){
     val vm = hiltViewModel<AccountViewModel(navController.getBackStackEntry("account"))
     //... ui ...
  }
}
//...

... the exception did not occure. The idea with the lifecycle issue came into my mind when I saw that the offical example has similar workarounds in place.

Summary

I actually do not know if I did something wrong or if I miss a conecept here. I can put the lifecycle-check-workaround into place but is this really as intended? Additional to that I did not find any hint in the doc regarding that.

Does anybody know how to fix that in a proper way?

Regards, Chris

There was an issue with the navigation component. It has been fixed for me with v2.4.0-alpha08

This is how you do it now but make sure you have the latest compose navigation artefacts:

private fun NavGraphBuilder.accountGraph(navController: NavHostController) {
   navigation(
      startDestination = "main",
      route = "account") {

       composable("main") {
          val parentEntry = remember {
            navController.getBackstackEntry("account")
          }
          val vm = hiltViewModel<AccountViewModel(parentEntry)
          //... ui ...
       }

       composable("login") {
          val parentEntry = remember {
            navController.getBackstackEntry("account")
          }
          val vm = hiltViewModel<AccountViewModel(parentEntry)
          //... ui ...
       }
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM