35

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.

Community
  • 1
  • 1
marmor
  • 27,641
  • 11
  • 107
  • 150
  • 2
    Check [this](https://groups.google.com/forum/?fromgroups#!topic/android-developers/O8z7-Q7XW38) and [this](https://groups.google.com/forum/?fromgroups#!topic/android-developers/Zpb8YSzTltA) – Alejandro Colorado Jun 11 '13 at 22:48
  • 1
    I had a similar error and the suggested link by Alejandro Colorado soved it. After a cursory look at the support library source it looks like that the Fragment state is "not active" (mIndex < 0). Due to the completely random nature of the bug (I have not been able to reproduce it), I would think that it's the expression of a problem deeply rooted in the Fragment/FragmentManager code... – Rick77 Jul 17 '13 at 13:32

12 Answers12

18

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();
        }
    }
Greg Ennis
  • 14,917
  • 2
  • 69
  • 74
11

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.

piusvelte
  • 1,596
  • 2
  • 15
  • 18
  • 1
    thanks, this sounds like a promising lead. I'll check it out. – marmor Oct 14 '14 at 08:07
  • @marmor You could override saveState in your Adapter implementation, and either skip calling super if the FragmentManager has no Fragments, and/or wrap super.saveState in a try/catch for IllegalStateException. – piusvelte Oct 15 '14 at 01:50
  • is this the solution because the patch is still not included ? – divyenduz Sep 16 '15 at 11:46
8

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

ViliusK
  • 11,345
  • 4
  • 67
  • 71
  • 1
    Thanks, but i'm not using fragments hierarchy, only 3 fragments under one activity. – marmor Oct 14 '13 at 05:23
  • I was using an activity with a fragment in it with a viewpager managing fragments. So changing to getChildFragmentManager() solved the problem for me – nsL Feb 27 '15 at 16:23
  • How can this be done using `ActionBarActivity`? All I have is `getSupportFragmentManager()` – Jonas Borggren May 13 '15 at 09:34
  • There is no access to getChildFragmentManager when we are on vanilla activity. – Renan Nery Aug 31 '15 at 14:44
8

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!

Joseph
  • 667
  • 6
  • 5
  • I recommend opening a bug report on the Android issue tracker, and since you already have a fix for it, you can even submit your fix directly to the Android Gerrit review system: https://android-review.googlesource.com/ – Joe Oct 02 '14 at 15:01
2

Use the following

final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(f, key);

instead of

mFragmentManager.putFragment(state, key, f);

and pass the bundle explicitly..

For reference,

http://developer.android.com/reference/android/app/FragmentTransaction.html#add%28android.app.Fragment,%20java.lang.String%29

sean
  • 3,955
  • 21
  • 28
Sripathi
  • 1,760
  • 15
  • 20
  • Thanks for the answer, but the code in question is from FragmentStatePagerAdapter which is a class by Android, not from my package. Re-read my question. – marmor Nov 20 '13 at 11:38
  • The FragmentStatePagerAdapter is used to maintain the state of the fragments. – Sripathi Nov 20 '13 at 11:54
  • It's used to display multiple fragments one besides the other, allowing the user to swipe between them, while also maintaining state. I'm using this class to show some fragments, and I'm getting the above crash rarely. – marmor Nov 20 '13 at 13:07
  • Like this case, I'm getting IllegalArgumentException when add a mapfragement to the fragmentmanager, even I after removed the previously added mapfragment. It shows like parent have such a child with same id. And I have implemented try catch, because I couldn't found no other way to resolve it.. But I'm analyzing the issue.. Get you back with exact solution.. – Sripathi Nov 21 '13 at 05:22
2

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

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

Thomas G.
  • 882
  • 9
  • 10
1

Write in Activity onCreate() method :

pager = (ViewPager) findViewById(R.id.pager);
    adapter = new SwipePagerAdapter(getSupportFragmentManager());
    pageOneFragment = new PageOneFragment();
    adapter.addFragment(pageOneFragment);

Adapter code:

public class SwipePagerAdapter extends FragmentStatePagerAdapter
{
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public SwipePagerAdapter(FragmentManager fm)
{
    super(fm);
}

@Override
public Fragment getItem(int position)
{
    return mFragments.get(position);
}

@Override
public int getCount()
{
    return mFragments.size();
}

public void addFragment(Fragment fragment)
{
    mFragments.add(fragment);
    notifyDataSetChanged();
}

@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
    super.destroyItem(container, position, object);
}

@Override
public CharSequence getPageTitle(int position)
{
    return super.getPageTitle(position);
}}
1

Are you calling FragmentStatePagerAdapter.instantiateItem() from your own code? Generally, the ViewPager should be the only client that calls this method, however, it's easy to come across workarounds for other limitations that rely on this method.

Due to the way FragmentStatePagerAdapter works, I would guess that the adapter has instantiated the Fragment, added it to it's internal collection of Fragments, and even began a transaction to add that Fragment to the FragmentManager, but that fragment transaction has not been committed or executed. This is exactly what instantiateItem() does when it hasn't already instantiated the requested Fragment.

Another method, FragmentStatePagerAdapter.finishUpdate() is responsible for committing the transaction as well as for executing the pending transactions. finishUpdate() must be called after instantiateItem() or else the Fragment may not be added to the FragmentManager.

Try something like this:

// Get the current Fragment presented by the ViewPager.
pagerAdapter.startUpdate(viewPager);
Fragment fragment = pagerAdapter.instantiateItem(viewPager, viewPager.getCurrentItem());
pagerAdapter.finishUpdate(viewPager);
return fragment;

startUpdate() has an empty implementation as of API level 19.

James Wald
  • 13,626
  • 5
  • 52
  • 63
0

You could try using mFragmentManager.add();

PaulG
  • 13,871
  • 9
  • 56
  • 78
superuser
  • 731
  • 10
  • 28
0

Try using

use fragmentTransaction.add()
Bedimindtricks
  • 113
  • 1
  • 7
0

Fragments in the ViewPager are fixed, instead of trying to replace the fragments in the adapter, try to give a different set of fragments and notifyDataSet changed, or take the advantage of FrameLayout to show another fragment over the view pager tab's current fragment.

There is my solution that works:

Swipe Gesture applied at Fragment level along with ViewPager with it's default swipe disabled

Community
  • 1
  • 1
Abhinav Saxena
  • 1,990
  • 2
  • 24
  • 55
0

Double-check whether you've implemented onSaveInstanceState and onRestoreInstanceState in your activity, and verify that they're correctly saving and loading the state of your fragments.

Hubert
  • 485
  • 7
  • 14