Search code examples
javaandroidandroid-viewmodelandroid-architecture-navigationandroid-viewpager2

ViewPager2/Tabs problem with ViewModel state


I am following the MVVM pattern - meaning I have a ViewModel for each Fragment.

I added two tabs by using ViewPager2.

My adapter looks like this:

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

The tabs are working. However, I noticed that the ViewModel of my MergedItemsFragment is behaving weirdly. Before I added tabs I navigated to the Fragment like this:

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

When I left that fragment with NavHostFragment.findNavController(this).popBackStack() and later on returned to that fragment I would get a new empty ViewModel. This was intended.

With the new approach I am navigating with return new MergedItemsFragment(). When I leave that fragment and later on return I am getting a ViewModel that contains the old data. That is an issue because the old data is not relevant anymore because the User selected different data in another fragment.


Update #1

I realized that he actually keeps all the old Fragments in memory because the same print statements gets called multiple times. The times it is called increases with the amount of times I leave and return to that screen. So if I leave and return 10 times and rotate my device he will actually execute one line 10 times. Any guesses how to implement Tabs/ViewPagers with Navigation Components in a manner that works with ViewModels?


Update #2

I set my ViewModels like this:

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

I get the same results with:

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

I bind the ViewModel in the Fragment itself. Therefore, this is the Fragment.


Solution

  • As per your comment, you are using Fragment and inside that Fragment there is your viewpager. So while creating your Adapter for ViewPager you need to pass childFragmentManager instead of getActivity()

    Below is a sample Adapter for your viewPager that you can use

    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]
        }
    }
    

    and while creating your adapter call it like

       val adapter = NewViewPagerAdapter(
            childFragmentManager,
            FragmentPagerAdapter.POSITION_UNCHANGED
        )
    

    as if you see the documentation for FragmentStatePagerAdapter it states that you should pass (FragmentManager, int) inside your adapter's constructor

    I hope this will solve your issue as I was facing the same issue one day.

    Happy coding.