Jetpack Compose 中的作用域状态
Posted
技术标签:
【中文标题】Jetpack Compose 中的作用域状态【英文标题】:Scoping States in Jetpack Compose 【发布时间】:2021-03-05 10:14:49 【问题描述】:在所有应用程序中,总会有以下三种状态范围:
使用 Compose,可以通过以下方式实现“每屏状态”:
NavHost(navController, startDestination = startRoute)
...
composable(route)
...
val perScreenViewModel = viewModel() // This will be different from
composable(route)
...
val perScreenViewModel = viewModel() // this instance
...
“应用状态”可以通过以下方式实现:
val appStateViewModel = viewModel()
NavHost(navController, startDestination = startRoute)
...
但是“作用域状态”呢?我们如何在 Compose 中实现它?
【问题讨论】:
如果您需要一个可行的解决方案;我目前使用 compose router Github。 【参考方案1】:这正是navigation graph scoped view models 的用途。
这涉及两个步骤:
找到与您希望将 ViewModel 范围限定为的图形关联的 NavBackStackEntry
将其传递给viewModel()
。
对于第 1 部分),您有两个选择。如果您知道导航图的路线(通常您应该知道),您可以直接使用getBackStackEntry
:
// 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)
但是,如果您想要更通用的东西,您可以使用目标本身中的信息检索返回堆栈条目 - 它的 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)
这允许您编写如下内容:
val parentEntry = it.rememberParentEntry()
val navigationGraphScopedViewModel = viewModel(parentEntry)
虽然parent
目标将等于简单导航图的根图,但当您使用nested navigation 时,父级是图的中间层之一:
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())
...
请注意,您不仅限于直接父级:每个父级导航图都可用于提供更大的范围。
【讨论】:
有没有办法知道composable
是否因为配置更改(如设备轮换)而被丢弃,而composable
是否因为不再需要而被完全丢弃?跨度>
当涉及到NavHost
中的具体行为时,范围内的 ViewModel 的 onCleared()
只会在该目标(或一组目标,如果您使用导航图范围内的 ViewModel ) 从返回堆栈中弹出并永久销毁。
嗨@ianhanniballake,我可以问另一个与这个问题有关的问题。我把它贴在这里:***.com/questions/65075735/…
这些实现是否有机会进入官方 api?
@ianhanniballake parentViewModel
会被重组吗? hilt example 用于获取范围为导航路线的 viewModel 使用 remember
【参考方案2】:
来自Compose and other libraries - Hilt 文档
要检索范围为导航路线的ViewModel
实例,请将目标根作为参数传递:
val loginBackStackEntry = remember navController.getBackStackEntry("Parent")
val loginViewModel: LoginViewModel = hiltViewModel(loginBackStackEntry)
没有 Hilt 也可以做到这一点
val loginBackStackEntry = remember navController.getBackStackEntry("Parent")
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
这实现了 @ianhanniballake 实现的相同目标,但代码更少
注意:导航图有自己的 route = "Parent"
完整代码示例
使用 Jetpack 组合和导航的作用域状态示例
// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry
@Composable
fun MyApp()
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)
【讨论】:
以上是关于Jetpack Compose 中的作用域状态的主要内容,如果未能解决你的问题,请参考以下文章
viewpager jetpack compose 中的垂直滚动不起作用
Recompose 方法在 Jetpack Compose 中不起作用