我想在许多可组合物之间共享一个视图模型。就像我们如何在Activity中的片段之间共享视图模型一样。
但是当我尝试这个
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
navigation(startDestination = "username", route = "login") {
// FIXME: I get an error here
val viewModel: LoginViewModel = viewModel()
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
}
}
我得到一个错误
@Composable调用只能在@Composable函数的上下文中发生
- 视图模型应该只在NavGraph作用域中是活动的。
- 当我去一个不同的路线,然后回来我应该初始化一个新的视图模型(这就是为什么我在NavGraph中调用它)
几乎相同的解
回答Philip Dukhov的问题如何在两个或多个Jetpack可组合件之间共享视图模型在撰写NavGraph?
但是在这种方法中,视图模型停留在启动它的活动的范围内,因此永远不会被垃圾收集。
解决方案1(从文档中复制)
导航返回堆栈不仅为每个单独的目的地存储NavBackStackEntry,而且为包含单个目的地的每个父导航图存储NavBackStackEntry。这允许您检索作用域为导航图的
NavBackStackEntry
。导航图作用域的NavBackStackEntry
提供了一种方法来创建作用域为导航图的ViewModel
,使您能够在图的目的地之间共享与ui相关的数据。以这种方式创建的任何ViewModel
对象一直存在,直到相关的NavHost
和它的ViewModelStore
被清除,或者直到导航图从后堆栈弹出。
这意味着我们可以使用NavBackStackEntry来获取我们所在的导航图的范围,并使用它作为ViewModelStoreOwner
来获取该范围的视图模型。
将此添加到每个可组合项中以获得login
的BackStackEntry
,然后将其用作ViewModelStoreOwner
以获得视图模型。
val loginBackStackEntry = remember { navController.getBackStackEntry("login") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
所以最后的代码变成了
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
navigation(startDestination = "username", route = "login") {
composable("username") {
val loginBackStackEntry = remember { navController.getBackStackEntry("login") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
...
}
composable("password") {
val loginBackStackEntry = remember { navController.getBackStackEntry("login") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
...
}
composable("registration") {
val loginBackStackEntry = remember { navController.getBackStackEntry("login") }
val loginViewModel: LoginViewModel = viewModel(loginBackStackEntry)
...
}
}
}
}
解决方案2
摘自ianhanniballake答案
也可以使用扩展名
来实现- 获取当前作用域并获取或创建该作用域的视图模型
@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()
}
- 然后简单地在你的导航图 中使用这个扩展
navigate(secondNestedRoute, startDestination = nestedStartRoute) {
composable(route) {
val loginViewModel: LoginViewModel = it.parentViewModel(navController)
}
}
首先,创建一个函数来获取导航ViewModelStoreOwner
。
@Composable
fun rememberParentViewModelStoreOwner(
navController: NavHostController,
parentRoute: String,
): ViewModelStoreOwner {
return remember(navController.currentBackStackEntry) {
object : ViewModelStoreOwner {
override val viewModelStore =
navController.getBackStackEntry(parentRoute).viewModelStore
}
}
}
然后,检索它并使用它在所需的可组合构建器中构造ViewModel
。
composable("username") {
val loginViewModelStoreOwner = rememberParentViewModelStoreOwner(navController, "login")
val loginViewModel: LoginViewModel = viewModel(loginViewModelStoreOwner)
// ...
}
composable("password") {
val loginViewModelStoreOwner = rememberParentViewModelStoreOwner(navController, "login")
val loginViewModel: LoginViewModel = viewModel(loginViewModelStoreOwner)
// ...
}
composable("registration") {
val loginViewModelStoreOwner = rememberParentViewModelStoreOwner(navController, "login")
val loginViewModel: LoginViewModel = viewModel(loginViewModelStoreOwner)
// ...
}
解释:
通常,ViewModel
是用可组合构建器的NavBackStackEntry
中的局部作用域ViewModelStoreOwner
(LocalViewModelStoreOwner.current
)构造的。如果已经用相同的ViewModelStoreOwner
构造了ViewModel
,它将重用前一个实例,而不是再次创建它。在本例中,我们将使用登录导航ViewModelStoreOwner
来构造ViewModel
。