سؤال

My Android app has a single activity with a tab controller hosting 4 tabs – each tab is represented by a Fragment. When I run the app on my test device running OS 4.03, the app works and I can navigate to different tabs, etc. When the app is run on a device with OS 4.3, the app crashes during startup with an IllegalStateException: Fragment already added: MyFirstTabFragment.

Here's the stack trace:

java.lang.IllegalStateException: Fragment already added: MyFirstTabFragment{41866dc0 #0 id=0x7f080000 MyFirstTabFragment} at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:1128) at android.app.BackStackRecord.run(BackStackRecord.java:616) at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435) at android.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:474) at android.support.v13.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:167) at android.support.v4.view.ViewPager.populate(ViewPager.java:1068) at android.support.v4.view.ViewPager.populate(ViewPager.java:914) at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436) at android.view.View.measure(View.java:15848) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5012) at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) at android.view.View.measure(View.java:15848) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5012) at com.android.internal.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:302) at android.view.View.measure(View.java:15848) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5012) at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2189) at android.view.View.measure(View.java:15848) at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1905) at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1104) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1284) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749) at android.view.Choreographer.doCallbacks(Choreographer.java:562) at android.view.Choreographer.doFrame(Choreographer.java:532) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735) at android.os.Handler.handleCallback(Handler.java:730) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:5103) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) at dalvik.system.NativeStart.main(Native Method)

The problem appears to be caused by attempting to add the fragments within a FragmentTransaction within the getItem() method of my FragmentStatePagerAdapter extended class as follows:

public class MyFragmentStatePagerAdapter extends android.support.v13.app.FragmentStatePagerAdapter {        

    @Override
    public Fragment getItem(int position) {
        if (position == 0) {
            Fragment fragment = new MyFirstTabFragment();
            getFragmentManager().beginTransaction().add(fragment, "MyFirstTabFragmentTag").commit();

         // getFragmentManager().beginTransaction().replace(R.id.pager, fragment, "MyFirstTabFragmentTag").commit();

            return fragment;  
        } else if (position == 1) {
            Fragment fragment = new MySecondTabFragment();
            getFragmentManager().beginTransaction().add(fragment, "MySecondTabFragmentTag").commit();

         // getFragmentManager().beginTransaction().replace(R.id.pager, fragment, "MySecondTabFragmentTag").commit();

            return fragment;  
        }
    }
.
.
.

}

The reason I am calling add() is so that I can associate a tag with my fragments in order to access those fragments later to invoke a method on those fragment instances as tabs are selected/unselected.

I have searched through various posts on the IllegalStateException and have tried various suggestions including calling replace() instead of add(), calling remove() then add(), calling MyFirstTabFragment.instantiate() instead of new MyFirstTabFragment() and none of these changes have corrected the problem.

I suspect that I may be doing this FragmentTransaction too early in the process since the ViewPager is currently in the process of adding the fragments when I attempt to add or replace the fragment with my tag. Does anyone have any better insights to this process? Is there a better place I can make these separate add() calls in order to tag my fragments?

Thanks in advance for any suggestions!

هل كانت مفيدة؟

المحلول

So just to complete this thread...

I removed the add() or replace() call in my adapter's getItem() method and simply instantiate the new fragments to avoid the IllegalStateException, but was unable to determine a logical place to move the replace() call so that I could tag the fragments. This is unfortunate as using the tag would be the most reliable solution.

I tried accessing the fragments using the internal "android:switcher..." tag, but that doesn't seem to work - looking at the tag of my fragments, the tag is always null - so maybe they changed the internal behavior to no longer generate tags by default.

I have resorted to maintaining my own collection of fragment instances that I populate during the getItem() call and update during the adapter's destroyItem() call. I hope there are no surprises along the way here that will cause my collection to have instances that are different than what the ViewPager/adapter are storing.

نصائح أخرى

Just return the fragment in your adapter's getItem, it will get added to the screen by the the viewpager on it's own when you call setAdapter.

@Override
public Fragment getItem(int position) {
    if (position == 0) {
        Fragment fragment = new MyFirstTabFragment();

        return fragment;  
    } else if (position == 1) {
        Fragment fragment = new MySecondTabFragment();

        return fragment;  
    }
}

You could have done something like this:

private Fragment[] pages = new Fragment[2];

@Override
public Fragment getItem(int position) {

     if(pages[position] == null){

        switch(position){

        case 0:
           pages[position] = new MyFirstTabFragment();
        break;

        case 1:
            pages[position] = new MySecondTabFragment();
        break:

        default:
        //you have to determine a default position
        break;
    }
  }

  return pages[position];
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    pages[position] = null;
}

So, you could access the adapter fragment instances just calling:

MyFirstTabFragment firstFragment = (MyFirstTabFragment)adapter.getItem(0);

It would not create another instance for the position informed.

This worked for us:

private FragmentManager fm;
private Map<Integer, Fragment> map;

@Override
public Fragment getItem(int position) {
    fm.executePendingTransactions();
    if (map == null)
        map = new HashMap<>();

    if (map.containsKey(position))
        return map.get(position);

    Fragment fragment = new Fragment();
    ...
    map.put(position, fragment);
    return fragment;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top