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) { ... composable(route) { ... val perScreenViewModel = viewModel() // This will be different from } composable(route) { ... val perScreenViewModel = viewModel() // this instance } ... }
The "App State" could be achieved by:
val appStateViewModel = viewModel() NavHost(navController, startDestination = startRoute) { ... }
But how about for "Scoped State"? How could we achieve it in Compose?
State hoisting in Compose is a pattern of moving state to a composable's caller to make a composable stateless. The general pattern for state hoisting in Jetpack Compose is to replace the state variable with two parameters: value: T : the current value to display.
Connecting a ViewModel state to an activityThe viewModel() function is provided by the Compose view model lifecycle library which needs to be added to the build dependencies of a project when working with view models as follows: You are reading a sample chapter from Jetpack Compose Essentials.
Use LaunchedEffect() when you are using coroutines and need to cancel and relaunch the coroutine every time your parameter changes and it isn't stored in a mutable state. DisposableEffect() is useful when you aren't using coroutines and need to dispose and relaunch the event every time your parameter changes.
A LazyColumn is a vertically scrolling list that only composes and lays out the currently visible items. It's similar to a Recyclerview in the classic Android View system.
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 to
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() val navigationGraphEntry = remember { navController.getBackStackEntry("graph_route") } val navigationGraphScopedViewModel = 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 parent val parentId = navBackStackEntry.destination.parent!!.id // Now get the NavBackStackEntry associated with the parent // making sure to remember it return remember { navController.getBackStackEntry(parentId) } }
Which allows you to write something like:
val parentEntry = it.rememberParentEntry() val navigationGraphScopedViewModel = 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With