Jetpack Compose 中的作用域状态

Arc*_*nes 18 android android-navigation android-viewmodel android-jetpack-compose

在所有应用程序中,总会有这三种状态范围: 状态

使用 Compose,可以通过以下方式实现“每个屏幕状态”:

NavHost(navController, startDestination = startRoute) {
    ...
    composable(route) {
       ...
       val perScreenViewModel = viewModel()  // This will be different from
    }
    composable(route) {
       ...
       val perScreenViewModel = viewModel()  // this instance
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

“应用程序状态”可以通过以下方式实现:

val appStateViewModel = viewModel()
NavHost(navController, startDestination = startRoute) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

但是对于“Scoped State”呢?我们如何在 Compose 中实现它?

ian*_*ake 18

这正是导航图作用域视图模型的用途。

虽然viewModel()API 不允许您直接访问导航图范围的 API(没有与 Fragment by navGraphViewModels()API等效的API),但您可以编写一个方法来执行此操作:

@Composable
fun <reified VM : ViewModel> NavBackStackEntry.parentViewModel(
    navController: NavController
): VM {
    // First, get the parent of the current destination
    // This always exists since every destination in your graph has a parent
    val parentId = destination.parent!!.id

    // Now get the NavBackStackEntry associated with the parent
    val parentBackStackEntry = navController.getBackStackEntry(parentId)

    // And since we can't use viewModel(), we use ViewModelProvider directly
    // to get the ViewModel instance, using the lifecycle-viewmodel-ktx extension
    return ViewModelProvider(parentBackStackEntry).get()
}
Run Code Online (Sandbox Code Playgroud)

使用Navigation Compose 1.0.0-alpha02 版本,您可以添加嵌套导航图,从而允许您编写如下图:

NavHost(navController, startDestination = startRoute) {
    ...
  navigate(nestedRoute, startDestination = nestedStartRoute) {
    composable(route) {
        ...
        // This instance will be the same
        val parentViewModel: YourViewModel = it.parentViewModel(navController)
    }
    composable(route) {
       ...
        // As this instance
        val parentViewModel: YourViewModel = it.parentViewModel(navController)
    }
  }
  navigate(secondNestedRoute, startDestination = nestedStartRoute) {
    composable(route) {
        ...
         // But this instance is different
         val parentViewModel: YourViewModel = it.parentViewModel(navController)
    }
  }
  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 = it.parentViewModel(navController)
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,您不仅限于直接父级:每个父级导航图都可用于提供更大的范围。

  • 请注意,“记住 {navController.getBackStackEntry(parentId)}” 可能会导致崩溃,并且现在会触发 lint 警告([此处有更多信息](https://issuetracker.google.com/issues/227382831))。解决方案是使用一个`backStackEntry`作为`remember`之类的键。`记住(navBackStackEntry){navController.getBackStackEntry(parentId)}` (4认同)

clm*_*mno 7

来自Compose 和其他库 - Hilt文档

要检索范围为导航路线的实例ViewModel,请将目标根作为参数传递:

val loginBackStackEntry = remember { navController.getBackStackEntry("Parent") }
val loginViewModel: LoginViewModel = hiltViewModel(loginBackStackEntry)
Run Code Online (Sandbox Code Playgroud)

没有 Hilt 也可以完成同样的操作

val loginBackStackEntry = remember { navController.getBackStackEntry("Parent") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
Run Code Online (Sandbox Code Playgroud)

这实现了@ianhanniballake所实现的相同效果,但代码更少

注意:导航图有自己的route =“Parent”

完整代码示例

使用 Jetpack 撰写和导航的范围状态示例

val loginBackStackEntry = remember { navController.getBackStackEntry("Parent") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
Run Code Online (Sandbox Code Playgroud)

  • 请注意,“记住 {navController.getBackStackEntry(parentId)}” 可能会导致崩溃,并且现在会触发 lint 警告([此处有更多信息](https://issuetracker.google.com/issues/227382831))。解决方案是使用一个`backStackEntry`作为`remember`之类的键。`记住(backStackEntry){navController.getBackStackEntry(parentId)}` (5认同)