2

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!

JavaJoe
  • 99
  • 1
  • 1
  • 10
  • Don't call add() and try other way to access those fragments. – Marcio J Dec 16 '13 at 19:21
  • Thanks for your reply. Can you suggest another way to access my fragments other than getFragmentManager().findFragmentByTag()? I need to do so from onTabSelected() as well as some other custom methods. – JavaJoe Dec 16 '13 at 19:37
  • you can use viewpager2 to solve this problem [see the approach](https://stackoverflow.com/a/59393854/2383176) – javadroid Dec 18 '19 at 14:19

4 Answers4

2

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.

JavaJoe
  • 99
  • 1
  • 1
  • 10
1

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;  
    }
}
petey
  • 16,914
  • 6
  • 65
  • 97
  • Thanks for your reply. Agreed that simply returning new instances of my fragments in getItem() allows ViewPager to add them, however, I need a mechanism for being able to retrieve the fragment instance associated with a tab at some later time (usually when a tab becomes viewable or is going out of view) so that I can call a method on the fragment class to perform some desired behavior then. When I researched how to do that, the suggested method to retrieve fragments was to add a tag to them, and the only way to add a tag is via add() or replace() within a FragmentTransaction. – JavaJoe Dec 16 '13 at 19:42
  • please check out http://stackoverflow.com/q/8785221/794088 (it has a few answers that will help you do this). – petey Dec 16 '13 at 19:52
  • Thanks for the link - I read through the suggestions and the proposed solutions have holes/dangers... 1)Maintaining my own collection of fragment references is prone to errors as this requires fully understanding ViewPager's behavior in order to ensure my collection is always in sync with ViewPager's fragment collection. 2)Calling findFragmentByTag() using ViewPager's internal fragment naming ("android:switcher:viewID:position") is dangerous as it relies on undocumented internal ViewPager implementation details. – JavaJoe Dec 17 '13 at 13:59
  • What this boils down to is attempting to come up with a safe and robust solution to address the absence of a getFragment() method on the ViewPager. So as to not try to implement some hack to achieve this, it would appear the add() or replace() methods exist to assign a custom tag to my fragments for subsequent retrieval via findFragmentByTag(). The question is, when is it safe to perform these calls during the startup process? In other words, where/when call I call replace() and be sure the fragments have already been populated in the ViewPager so I can avoid this IllegalStateException? – JavaJoe Dec 17 '13 at 14:07
0

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.

Cícero Moura
  • 2,027
  • 1
  • 24
  • 36
0

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;
}
doctorram
  • 1,112
  • 14
  • 13