Jetpack Compose 中的范围状态
Scoping States in Jetpack Compose
在所有应用程序中,总会有这三种状态范围:
使用 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) {
...
}
但是“Scoped State”呢?我们如何在 Compose 中实现它?
这正是 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())
}
...
}
请注意,您不仅限于直接父级:每个父级导航图都可用于提供更大的范围。
来自 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)
这实现了与@ 相同的效果,但代码更少
注意:导航图有自己的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)
}
}
}
}
在所有应用程序中,总会有这三种状态范围:
使用 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) {
...
}
但是“Scoped State”呢?我们如何在 Compose 中实现它?
这正是 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())
}
...
}
请注意,您不仅限于直接父级:每个父级导航图都可用于提供更大的范围。
来自 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)
这实现了与@
注意:导航图有自己的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)
}
}
}
}