ViewPager2/Tabs与ViewModel状态的问题

10

我正在遵循MVVM模式 - 这意味着我为每个Fragment都有一个ViewModel。

我使用ViewPager2添加了两个选项卡。

我的适配器看起来像这样:

@Override
public Fragment createFragment(int position) {
    switch (position) {
        case 0:
            return new MergedItemsFragment();
        case 1:     
            return new ValidatedMergedItemsFragment();
    }
    return new MergedItemsFragment();
}

选项卡正常工作。但是,我注意到我的MergedItemsFragment的ViewModel表现出奇怪的行为。 在我添加选项卡之前,我是这样导航到片段的:

NavHostFragment.findNavController(this).navigate(R.id.action_roomFragment_to_itemsFragment);

当我使用 NavHostFragment.findNavController(this).popBackStack() 离开该片段并稍后返回该片段时,我将获得一个新的空的 ViewModel。 这是有意为之的。

采用新方法,我使用 return new MergedItemsFragment() 进行导航。当我离开该片段并稍后返回时,我会得到一个包含旧数据的 ViewModel。这是一个问题,因为旧数据已不再相关,用户在另一个片段中选择了不同的数据。


更新 #1

我意识到他实际上将 所有 旧的 片段 保存在内存中,因为同一条打印语句会被多次调用。它被调用的次数随着我离开和返回该屏幕的次数而增加。因此,如果我离开并返回 10 次,并旋转我的设备,他实际上会执行一行代码 10 次。 您猜如何以一种适用于 ViewModel 的方式实现具有导航组件的选项卡/视图分页器?


更新 #2

我像这样设置了我的 ViewModel:

viewModel = new ViewModelProvider(this, providerFactory).get(MergedItemViewModel.class)

我使用相同的方法得到了相同的结果:

viewModel = ViewModelProviders.of(this).get(MergedItemViewModel.class);

我在Fragment本身绑定了ViewModel。因此,this是指的是该Fragment本身。


你能展示一下你是如何设置你的ViewModels吗?此外,你不能在获取新数据时创建一个新的ViewModel吗? - BlackHatSamurai
以前你不需要这样做,因为片段已经被销毁了。现在你正在使用ViewPager,它会将片段存储在内存中。我建议只在需要时清除数据。你需要在VM中管理数据,而不是VM本身。 - BlackHatSamurai
我为MergedItemsFragment使用了一个不同的VM,为ValidatedMergedItemsFragment使用了一个共享的VM(作用域为活动)。但是,即使我完全移除第二个片段,我认为错误仍然存在。 - user123456789
@user123456789 - 你应该传递 Fragment(即 this)而不是 Activity。这是使用 childFragmentManager 的方法。 - ianhanniballake
@user123456789 在你的 ViewPagerFragmentAdapter 构造函数中传递 FragmentManager 和 int behavior,在创建适配器时传递 childFragmentManager, FragmentPagerAdapter.POSITION_UNCHANGED。这将解决你的问题。 - Rakshit Nawani
显示剩余5条评论
2个回答

6
根据您的评论,您正在使用Fragment,并且在该Fragment中有您的viewpager。因此,在为ViewPager创建适配器时,您需要传递childFragmentManager而不是getActivity()
以下是您可以使用的viewPager的示例适配器。
class NewViewPagerAdapter(fm: FragmentManager, behavior: Int) : FragmentStatePagerAdapter(fm, behavior) {
    private val mFragmentList: MutableList<Fragment> = ArrayList()
    private val mFragmentTitleList: MutableList<String> = ArrayList()

    override fun getItem(position: Int): Fragment {
        return mFragmentList[position]
    }

    override fun getCount(): Int {
        return mFragmentList.size
    }

    fun addFragment(fragment: Fragment, title: String) {
        mFragmentList.add(fragment)
        mFragmentTitleList.add(title)
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return mFragmentTitleList[position]
    }
}

在创建适配器时,请按以下方式调用:

   val adapter = NewViewPagerAdapter(
        childFragmentManager,
        FragmentPagerAdapter.POSITION_UNCHANGED
    )

如果您查看 FragmentStatePagerAdapter 的文档,会发现在适配器构造函数中应该传入 (FragmentManager, int) 参数。

希望这能解决您的问题,因为我曾经也遇到过同样的问题。

祝编码愉快。


1
谢谢。正如ianhanniballake所说,仅传递片段本身就足够了,只需确保您有一个合适的构造函数即可。因此,两个答案都是正确的。 - user123456789

1
new ViewModelProvider(requireActivity()).get("your_key", YourViewModel.class)

使用requireActivity获取ViewModel而不是片段。

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