Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scoping States in Jetpack Compose

Tags:

In all applications there will always be this three scopes of state: States

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?

like image 345
Archie G. Quiñones Avatar asked Nov 22 '20 15:11

Archie G. Quiñones


People also ask

What is state hoisting in jetpack 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.

How do you use ViewModel in jetpack compose?

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.

When should I use LaunchedEffect?

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.

What is LazyColumn in jetpack compose?

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.


1 Answers

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() 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.

like image 52
ianhanniballake Avatar answered Sep 30 '22 03:09

ianhanniballake