IllegalStateException: <MyFragment> is not currently in the FragmentManager

22,597

Solution 1

The FragmentStatePagerAdapter is a horrible piece of code riddled with bugs acknowledge or not by Google and so I use this code to fix this particular crash:

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // Yet another bug in FragmentStatePagerAdapter that destroyItem is called on fragment that hasnt been added. Need to catch
        try {
            super.destroyItem(container, position, object);
        } catch (IllegalStateException ex) {
            ex.printStackTrace();
        }
    }

Solution 2

This can happen when a FragmentStatePagerAdapter is set on a ViewPager, with the Fragments being populated, but not yet added to the FragmentManager due to the Activity pausing before layout has occurred for the ViewPager. This is reliably hit by starting another Activity from onCreate, with the Adapter also set in onCreate. In that situation, it's best to hold off on setting the Adapter, and set it onActivityResult instead. See this issue: https://code.google.com/p/android/issues/detail?id=77285

I've also submitted a patch to check for the Fragment having been added before trying to save state.

Solution 3

I've encountered the exact exception.

In my case, I have several fragments managed by FragmentPagerStateAdapter, but sometimes the fragment will get switched in position.

I override getItemPosition() and return the new position and call notifyDataSetChanged() to refresh ViewPager. Everything works fine but sometimes when I leave the ViewPager the crash occurs.

After several hours digging into it, I found there's a bug in FragmentPagerStateAdapter.

The adapter not only caches states, but also caches the fragments that it consider 'active'

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

But it does not check if the fragments' position has invalidated or moved.

That's how it get this exception:

1.I have A,B,C three Fragments in ViewPager and Adapter.

2.I switched the position to B,A,C in adapter and called notifyDataSetChanged().

3.ViewPager re-layout the fragment by new order. But the mFragments cache is still {A,B,C}

4.Swipe few pages, ViewPager will ask adapter to destroy the first Fragment. then the fragment B, instead of A, is set to null in the cache. Now the mFragments is {null,A,C}.

5.Leave the ViewPager, and onSaveInstanceState is triggered. all fragments in mFragments is considered active and putFragment() is called until FragmentManagerImpl found A not in it and throw a Exception.

So here's my not so gentle solution:

1.Copy the entire FragmentPagerStateAdapter source code.

2.Override the notifyDataSetChanged() method to rearrange the caches as below.

@Override
public void notifyDataSetChanged() {
  List<Fragment> oldFragments = new ArrayList<>(mFragments);
  List<Fragment.SavedState> oldStates = new ArrayList<>(mSavedState);
  for (int i = 0; i < getCount(); i++) {
    if (i < mFragments.size()) {
      Fragment f = mFragments.get(i);
      if (f != null) {
        int newPosition = getItemPosition(f);
        if (newPosition == POSITION_UNCHANGED || newPosition == i) {
        } else if (newPosition == POSITION_NONE) {
          if (i < mSavedState.size()) {
            mSavedState.set(i, null);
          }
          mFragments.set(i, null);
        } else {
          while (i >= mFragments.size()) {
            mFragments.add(null);
          }
          if (oldStates.size() > i) {
            mSavedState.set(newPosition, oldStates.get(i));
          }
          mFragments.set(newPosition, oldFragments.get(i));
        }
      } else {
        /*
         * No Fragment but that's possible there's savedState and position has
         * changed.
         */
      }
    }
  }
  super.notifyDataSetChanged();
}

Hope that works for some of you!

Solution 4

Use getChildFragmentManager() instead of getFragmentManager() if you have fragments hierarchy. eg. if you have fragments pager.

Solution 5

If your ViewPager is layouted inside a fragment (not an activty) :

mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getChildFragmentManager()));

Share:
22,597
marmor
Author by

marmor

Updated on November 30, 2020

Comments

  • marmor
    marmor over 3 years

    I know it sounds like a duplicate of FragmentStatePagerAdapter IllegalStateException: <MyFragment> is not currently in the FragmentManager but his solution isn't relevant to my case.

    I'm getting the following crash very rarely:

    java.lang.RuntimeException: Unable to pause activity {MyActivity}:

    ...

    Caused by: java.lang.IllegalStateException: Fragment MyFragment {40648258 id=0x7f070051} is not currently in the FragmentManager at android.support.v4.app.FragmentManagerImpl.putFragment(MT:516) at android.support.v4.app.FragmentStatePagerAdapter.saveState(MT:185) at android.support.v4.view.ViewPager.onSaveInstanceState(MT:881)

    ...

    at android.view.View.saveHierarchyState(View.java:6238) at com.android.internal.policy.impl.PhoneWindow.saveHierarchyState(PhoneWindow.java:1522) at android.app.Activity.onSaveInstanceState(Activity.java:1138) at android.support.v4.app.FragmentActivity.onSaveInstanceState(MT:480) at MyActivity.onSaveInstanceState(MT:336)

    It seems like this is the weird code I can't understand from FragmentStatePagerAdapter:

    for (int i=0; i<mFragments.size(); i++) {
        Fragment f = mFragments.get(i);
        if (f != null) {
            if (state == null) {
                state = new Bundle();
            }
            String key = "f" + i;
            mFragmentManager.putFragment(state, key, f);
        }
    }
    

    It looks like the adapter gets my Fragment from mFragments but can't add its state to FragmentManager.

    I've couldn't find any way to recreate this on my test devices, only received this from some users.

    I'm using support package v4.

    Any help? Thanks.