4

Here is the more of stacktrace

    java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.
        at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:557)
        at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:349)
        at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:462)
        at com.example.HomeFragmentContainer.onResume(HomeFragmentContainer.kt:76)
        at androidx.fragment.app.Fragment.performResume(Fragment.java:2747)

I think it's the same problem as Expected the adapter to be 'fresh' while restoring state

HomPagerAdaptor

class HomPagerAdaptor(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount() = 2;
    override fun createFragment(position: Int) = when (position) {
        0 -> TimelineContainer()
        else -> HomeFragment()
    }
}

I'm using adaptor as

class HomeFragmentContainer : DaggerFragment() {

    private val adapter by lazy {  HomPagerAdaptor(this) }
...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding!!.vpContainer.adapter = adapter
    }
...
    override fun onDestroyView() {
        binding?.vpContainer?.adapter = null
        binding = null
        super.onDestroyView()
    }
}

library versions:

 androidx.viewpager2:viewpager2:1.1.0-alpha01
 androidx.recyclerview:recyclerview:1.2.0-alpha03

I did little bit of digging and found that when I set adaptor to null the underlying recyclerview's views are recycled and the adaptor trie to remove fragments by calling removeFragment()

private void removeFragment(long itemId) {
    Fragment fragment = mFragments.get(itemId);

    ...
    if (shouldDelayFragmentTransactions()) {
          mHasStaleFragments = true;
          return;
     }
     try {
            mFragmentManager.beginTransaction().remove(fragment).commitNow();
            mFragments.remove(itemId);
        } finally {
            mFragmentEventDispatcher.dispatchPostEvents(onPost);
        }
}

  boolean shouldDelayFragmentTransactions() {
        return mFragmentManager.isStateSaved();
    }

// in FragmentManager
   public boolean isStateSaved() {
        // See saveAllState() for the explanation of this.  We do this for
        // all platform versions, to keep our behavior more consistent between
        // them.
        return mStateSaved || mStopped;
    }

But the shouldDelayFragmentTransactions() returns true because the fragment is already stopped and the fragments are not removed from adaptor. Later when the fragment pops back and sets the adapter in onViewCreated() the adaptor tries to restore state, but before doing that, it checks if there are any stale fragments in adaptor as

  @Override
    public final void restoreState(@NonNull Parcelable savedState) {
        if (!mSavedStates.isEmpty() || !mFragments.isEmpty()) {
            throw new IllegalStateException(
                    "Expected the adapter to be 'fresh' while restoring state.");
        }

It fixes the problem when I set adaptor to null in onResume() or onStop() but the viewpager's state is not restored later and defeats the whole purpose of using FragmentStateAdapter.

Edit: added more info

Sujan Poudel
  • 794
  • 5
  • 16
  • You'll need to create new `HomPagerAdaptor(this)` every time it's assigned to `vpContainer.adapter`. – shinwan May 15 '20 at 16:33

0 Answers0