Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prevent "java.lang.IllegalStateException: Fragment already added" when replacing fragments?

Despite my efforts to prevent fragments from being added more than once, I continue to encounter java.lang.IllegalStateException: Fragment already added: VideoFragment.

I have an activity where VideoFragment is instantiated in onCreate only. In the only place I attempt to display the VideoFragment, I first check whether this fragment has been added already.

private VideoFragment videoFragment;

public void onCreate(Bundle savedInstanceState) {
    ...
    videoFragment = new VideoFragment();
    ...
}

private void showVideoFragment() {
    if (!videoFragment.isAdded()) {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container, videoFragment, "video").commit();
    }
}

I have not been able to consistently reproduce this problem to examine in the debugger, but my runtime error reporting continues to report the exception java.lang.IllegalStateException: Fragment already added: VideoFragment for users, with stack traces composed of Android classes.

/FragmentManager.java:1133→ android.app.FragmentManagerImpl.addFragment
/BackStackRecord.java:648→ android.app.BackStackRecord.run
/FragmentManager.java:1453→ android.app.FragmentManagerImpl.execPendingActions
/FragmentManager.java:443→ android.app.FragmentManagerImpl$1.run
/Handler.java:733→ android.os.Handler.handleCallback
/Handler.java:95→ android.os.Handler.dispatchMessage
/Looper.java:146→ android.os.Looper.loop
/ActivityThread.java:5487→ android.app.ActivityThread.main
/Method.java:-2→ java.lang.reflect.Method.invokeNative
/Method.java:515→ java.lang.reflect.Method.invoke
/ZygoteInit.java:1283→ com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run
/ZygoteInit.java:1099→ com.android.internal.os.ZygoteInit.main
/NativeStart.java:-2→ dalvik.system.NativeStart.main

Does the definition of added in isAdded() not match the one used to check fragment transactions?

Or is there some way the videoFragment reference in the activity is not the same? Is this something I need to explicitly handle when saving state http://developer.android.com/guide/components/activities.html#SavingActivityState?

Or is there a reliable alternative way of checking whether the fragment has already been added?


UPDATE

I have figured out how to semi-reliably produce the problem.

  1. Start application
  2. Navigate away from application, and run other programs for awhile. On my Galaxy Nexus (which is pretty slow these days), using Chrome to read a few news articles seems sufficient. When returning to the home screen, if it takes a few seconds to render then the application will likely throw the fragment exception.
  3. Restart application and trigger fragment change

If I kill and simply run the application, everything appears to be fine. Or if I navigate away from the application and come back immediately, it works okay. It's only if the application is left in the background for a bit (enough to remove from memory?), that the fragment issue appears.

I also tried, to no effect, in onCreate

View v = findViewById(R.id.fragment_container);
if(v != null){
    Log.d(TAG, "disabling save for fragment_container");
    v.setSaveEnabled(false);
    v.setSaveFromParentEnabled(false);
}

I also tried checking Fragment prior = getFragmentManager().findFragmentByTag("video"); and Fragment prior2 = getFragmentManager().findFragmentById(R.id.fragment_container); before running the replace fragment transaction, but these come up null.

My problem in fact looks very similar to https://code.google.com/p/android/issues/detail?id=61247 though the time appears less an issue than memory/cache effects. It is completely unclear to me why that issue was closed.

I will try to produce a simple application that replicates this issue. My current one uses webrtc, and the logcat output is completely cluttered with webrtc messages.

like image 828
mattm Avatar asked Jun 03 '15 18:06

mattm


1 Answers

I see a few things here:

  1. Your problem occours propably when Activity is recreated by the system. You can simply simulate it by changing device orientation.
  2. isAdded() returns false, because Activity was recreated so this method is called for new instatnce of VideoFragment which hasn't knowledge about previous add.
  3. showVideoFragment() actually add fragment to Activity instead of just show it. I suggest you rename that method to somethink like "addVideoFragment" and move it to onCreate() method. If you do that you resolve the issue.
  4. If you really want to show or hide fragment use methods from FragmentTransaction eg:

     FragmentManager fm = getFragmentManager();
     fm.beginTransaction()
          .setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
          .show(somefrag) // or hide
          .commit();
    

Hint:
When you a priori know that you fragment is always the VideoFragment you can simply use:

<fragment 
     android:name="com.example.VideoFragment"
     android:id="@+id/video_fragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent" />

find it:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fragment_layout);
    VideoFragment fragment = (VideoFragment) getFragmentManager().findFragmentById(R.id.video_fragmen);
}

and make whatever you want with the instance.

like image 65
klimat Avatar answered Sep 28 '22 11:09

klimat