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 中的作用域状态的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack Compose中的副作用Api

viewpager jetpack compose 中的垂直滚动不起作用

Jetpack Compose Effect 的作用

Recompose 方法在 Jetpack Compose 中不起作用

Jetpack Compose“onSurface”颜色不起作用

Jetpack Compose Effect 的作用