Проблема ViewPager2 / Tabs с состоянием ViewModel

9

Я следую шаблону MVVM - это значит, что у меня есть ViewModel для каждого фрагмента.

Я добавил две вкладки с помощью ViewPager2.

Мой адаптер выглядит так:

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

Вкладки работают. Однако я заметил, что ViewModel моего MergedItemsFragment ведет себя странно. Прежде чем добавить вкладки, я перешел к фрагменту следующим образом:

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

Когда я оставил этот фрагмент с, NavHostFragment.findNavController(this).popBackStack()а позже вернулся к этому фрагменту, я получил бы новую пустую ViewModel. Это было предназначено.

С новым подходом я ориентируюсь return new MergedItemsFragment(). Когда я оставляю этот фрагмент и позже возвращаюсь, я получаю ViewModel, который содержит старые данные . Это проблема, потому что старые данные больше не актуальны, потому что Пользователь выбрал другие данные в другом фрагменте.


Обновление № 1

Я понял, что он на самом деле хранит все старые фрагменты в памяти, потому что одни и те же операторы печати вызываются несколько раз. Время его вызова увеличивается с количеством раз, когда я ухожу и возвращаюсь к этому экрану. Поэтому, если я уйду и вернусь 10 раз и поверну свое устройство, он фактически выполнит одну строку 10 раз. Кто-нибудь догадывается, как реализовать вкладки / ViewPager с компонентами навигации в манере, которая работает с ViewModels?


Обновление № 2

Я установил свои ViewModels так:

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

Я получаю те же результаты с:

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

Я связываю ViewModel в самом фрагменте. Следовательно, thisэто фрагмент.

user123456789
источник
Можете ли вы показать, как вы настраиваете свои ViewModels? Кроме того, есть ли причина, по которой вы не можете просто создать новую модель представления при получении новых данных?
BlackHatSamurai
Я обновил свой вопрос. Разве точка зрения модели не в том, что она сама позаботится об этом? Я создаю его один раз, и он сохраняется для одного фрагмента. Как именно я воссоздаю его, если у меня есть новые данные и почему мне не нужно было это делать ранее?
user123456789
Вам не нужно было делать это раньше, потому что фрагмент был уничтожен. Теперь вы используете ViewPager, и он сохраняет фрагмент в памяти. Я бы предложил просто очистить данные, когда вам нужно. Вам нужно управлять данными в ВМ, а не самой ВМ.
BlackHatSamurai
У меня проблема в том, что старые виртуальные машины все еще обслуживают старые LiveData и подают в другие компоненты старые данные. Поэтому очистка данных не поможет, потому что старые виртуальные машины продолжают мешать. Пример: я очищаю список в текущей ViewModel. Однако экран все еще получает старый список. Когда я отлаживаю ViewModel и проверяю длину списка, он говорит 0 - так как он был очищен. Единственным логическим объяснением являются другие ViewModels, обслуживающие старые данные.
user123456789
Используете ли вы одну и ту же виртуальную машину для каждого из фрагментов? Или у каждого фрагмента есть своя виртуальная машина?
BlackHatSamurai

Ответы:

3

Согласно вашему комментарию, вы используете 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
Спасибо. Как уже сказала Ианханнибаллейк, достаточно передать сам фрагмент, просто убедитесь, что у вас есть подходящий конструктор. Так что оба ответа верны.
user123456789