如何使用 navGraph 范围初始化 viewModel

How to initilaize viewModel using navGraph scope

我开始学习共享视图模型。 目前我在 activity 中有 3 个片段,其中 2 个在嵌套的 navGraph 中。

我想为它们创建共享的 navGraph viewModel 范围,但我不明白如何以及在何处初始化这些片段中的视图模型。

在我过去的所有应用程序中,我创建了全局视图模型

private lateinit var viewModel: MainViewModel

然后在 onCreateView 中,我像这样初始化 viewModel -

viewModel = ViewModelProvider(this, Factory(requireActivity().application)).get(
   MainViewModel::class.java)

如果我想与 2 个片段共享一个视图模型,我该如何对 navGraph viewModel 范围执行相同的操作?

目前我的做法是:

private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)

这是有效的,但是

A. 我从来没有在全局变量中看到 viewModel 是正确的

B. 我不能用这种方法在工厂内部传递变量

private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)

And It's work, but

A. I never saw viewModel initialized right in the global variable

B. I can't pass variables inside factory with this approach

A.) 在这种情况下,ViewModel 在第一次访问时被初始化,所以如果您只是在 onCreateonViewCreated 中键入 homeViewModel,那么它将在正确的范围内创建。

B.) 这是事实,你绝对可以使用带有 navGraphViewModels 的自定义工厂,但你真正想要的(可能)是隐式地将你的任何 Fragment 参数传递给 ViewModel(注意你的两个片段都必须在它们的参数中有正确的键才能安全地工作)通过使用 SavedStateHandle.

要获得 SavedStateHandle,您需要使用 AbstractSavedStateViewModelFactory。要创建一个,您必须在 onViewCreated 内创建您的 ViewModel(onCreate 不适用于导航图),使用 ViewModelLazy.

最容易做到这一点

要创建 viewModelLazy,您可以使用 createViewModelLazy(如上所示)。这可以为您定义一种传递 ViewModelStoreOwner(即 NavBackStackEntry)和 SavedStateRegistryOwner(也是 NavBackStackEntry)的方式。

所以你可以把它放在你的代码中,它应该可以工作。

inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
    arguments: Bundle,
    crossinline creator: (SavedStateHandle) -> T
): ViewModelProvider.Factory {
    return object : AbstractSavedStateViewModelFactory(this, arguments) {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(
            key: String, modelClass: Class<T>, handle: SavedStateHandle
        ): T = creator(handle) as T
    }
}

inline fun <reified T : ViewModel> Fragment.navGraphSavedStateViewModels(
    @IdRes navGraphId: Int,
    crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
    // Wrapped in lazy to not search the NavController each time we want the backStackEntry
    val backStackEntry by lazy { findNavController().getBackStackEntry(navGraphId) }

    return createViewModelLazy(T::class, storeProducer = {
        backStackEntry.viewModelStore
    }, factoryProducer = {
        backStackEntry.createAbstractSavedStateViewModelFactory(
            arguments = backStackEntry.arguments ?: Bundle(), creator = creator
        )
    })
}

inline fun <reified T : ViewModel> Fragment.fragmentSavedStateViewModels(
    crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
    return createViewModelLazy(T::class, storeProducer = {
        viewModelStore
    }, factoryProducer = {
        createAbstractSavedStateViewModelFactory(arguments ?: Bundle(), creator)
    })
}

@Suppress("UNCHECKED_CAST")
inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
    crossinline creator: () -> T
): Lazy<T> {
    return createViewModelLazy(T::class, storeProducer = {
        viewModelStore
    }, factoryProducer = {
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(
                modelClass: Class<T>
            ): T = creator.invoke() as T
        }
    })
}

现在你可以做

private val homeViewModel: HomeViewModel by navGraphSavedStateViewModels(R.id.nested_navigation) { savedStateHandle ->
    HomeViewModel(savedStateHandle)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view)

    homeViewModel.someData.observe(viewLifecycleOwner) { someData ->
        ...
    }
}