Scoping States In Jetpack Compose
Solution 1:
This is precisely what navigation graph scoped view models are used for.
This involves two steps:
Finding the
NavBackStackEntry
associated with the graph you want to scope the ViewModel toPass 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"