Skip to content Skip to sidebar Skip to footer

Scoping States In Jetpack Compose

In all applications there will always be this three scopes of state: With Compose, a 'Per Screen State' could be achieved by: NavHost(navController, startDestination = startRoute)

Solution 1:

This is precisely what navigation graph scoped view models are used for.

This involves two steps:

  1. Finding the NavBackStackEntry associated with the graph you want to scope the ViewModel to

  2. Pass that to viewModel().

For part 1), you have two options. If you know the route of the navigation graph (which, in general, you should), you can use getBackStackEntry directly:

// Note that you must always use remember with getBackStackEntry// as this ensures that the graph is always available, even while// your destination is animated out after a popBackStack()valnavigationGraphEntry= remember {
  navController.getBackStackEntry("graph_route")
}
valnavigationGraphScopedViewModel= viewModel(navigationGraphEntry)

However, if you want something more generic, you can retrieve the back stack entry by using the information in the destination itself - its parent:

fun NavBackStackEntry.rememberParentEntry(): NavBackStackEntry {
  // First, get the parent of the current destination// This always exists since every destination in your graph has a parentval parentId = navBackStackEntry.destination.parent!!.id

  // Now get the NavBackStackEntry associated with the parent// making sure to remember itreturn remember {
    navController.getBackStackEntry(parentId)
  }
}

Which allows you to write something like:

valparentEntry= it.rememberParentEntry()
valnavigationGraphScopedViewModel= viewModel(parentEntry)

While the parent destination will be equal to the root graph for a simple navigation graph, when you use nested navigation, the parent is one of the intermediate layers of your graph:

NavHost(navController, startDestination = startRoute) {
    ...
  navigation(startDestination = nestedStartRoute, route = nestedRoute) {
    composable(route) {
      // This instance will be the same
      val parentViewModel: YourViewModel = viewModel(it.rememberParentEntry())
    }
    composable(route) {
      // As this instance
      val parentViewModel: YourViewModel = viewModel(it.rememberParentEntry())
    }
  }
  navigation(startDestination = nestedStartRoute, route = secondNestedRoute) {
    composable(route) {
        // But this instance is different
      val parentViewModel: YourViewModel = viewModel(it.rememberParentEntry())
    }
  }
  composable(route) {
     // This is also different (the parent is the root graph)// but the root graph has the same scope as the whole NavHost// so this isn't particularly helpful
     val parentViewModel: YourViewModel = viewModel(it.rememberParentEntry())
  }
  ...
}

Note that you are not limited to only the direct parent: every parent navigation graph can be used to provide larger scopes.

Solution 2:

From the Compose and other libraries - Hilt doc

To retrieve the instance of a ViewModel scoped to navigation routes, pass the destination root as a parameter:

val loginBackStackEntry = remember { navController.getBackStackEntry("Parent") }
val loginViewModel: LoginViewModel = hiltViewModel(loginBackStackEntry)

The same can be done without Hilt

val loginBackStackEntry = remember { navController.getBackStackEntry("Parent") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)

This achieves the same thing acheived by @ianhanniballake but lesser code

Note: The navigation graph has its own route = "Parent"

Full code example

Scoped State Example with Jetpack compose and navigation

// import androidx.hilt.navigation.compose.hiltViewModel// import androidx.navigation.compose.getBackStackEntry@ComposablefunMyApp() {
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember {navController.getBackStackEntry("Parent")}
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

Post a Comment for "Scoping States In Jetpack Compose"