如何在 NavGraph 组件之间共享视图模型(仅限)

How to share a viewmodel between NavGraph components (only)

我想在多个可组合项之间共享一个视图模型。就像我们如何在 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 invocations can only happen from the context of a @Composable function

需要

几乎相似的解决方案

  1. 回答者Philip Dukhov for the question

    但在这种方法中,viewmodel 停留在启动它的 activity 的范围内,因此永远不会被垃圾收集。

解决方案 1

(从docs复制)

The Navigation back stack stores a NavBackStackEntry not only for each individual destination, but also for each parent navigation graph that contains the individual destination. This allows you to retrieve a NavBackStackEntry that is scoped to a navigation graph. A navigation graph-scoped NavBackStackEntry provides a way to create a ViewModel that's scoped to a navigation graph, enabling you to share UI-related data between the graph's destinations. Any ViewModel objects created in this way live until the associated NavHost and its ViewModelStore are cleared or until the navigation graph is popped from the back stack.

这意味着我们可以使用 NavBackStackEntry 来获取我们所在的导航图的范围,并将其用作 ViewModelStoreOwner 来获取该范围的视图模型。

在每个可组合项中添加它以获得 loginBackStackEntry,然后将其用作 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

Copied from answer

这也可以使用 extension

来实现
  1. 获取当前范围并为该范围获取或创建视图模型
@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()
}
  1. 然后只需在您的导航图中使用此扩展程序
navigate(secondNestedRoute, startDestination = nestedStartRoute) {
  composable(route) {
    val loginViewModel: LoginViewModel = it.parentViewModel(navController)
  }
}