如何使用 navGraph 范围来初始化 viewModel

7

我开始学习共享视图模型。目前,我的活动内有三个片段,其中两个位于嵌套的导航图中。

我想创建一个共享导航图ViewModel范围,供这两个片段使用,但我不知道在哪里和如何初始化这些片段内的视图模型。

在我以前的应用程序中,我创建了全局视图模型。

private lateinit var viewModel: MainViewModel

然后在 onCreateView 方法中,我像这样初始化了 viewModel:

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

如果我想使用navGraph viewModel范围来共享一个视图模型,我应该如何操作?

目前我有以下做法:

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

它可以正常工作,但是:

A. 我从未看到viewModel正确地初始化在全局变量中

B. 我无法使用这种方法在工厂内传递变量

1个回答

11
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和自定义工厂,但你真正想要的(可能)是通过使用SavedStateHandle来隐式传递Fragment参数到ViewModel(请注意,两个Fragment必须在其参数中具有正确的键才能安全地使用此方法),为了获得SavedStateHandle,您需要使用AbstractSavedStateViewModelFactory。要创建一个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 ->
        ...
    }
}

1
有人可以指导我如何进行这些扩展函数的单元测试吗? - Anne
尝试在你的测试中创建一个ViewModelStore和一个ViewModelStoreOwner。 - EpicPandaForce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接