mattm mattm - 1 month ago 19
Java Question

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.

Answer

I think I have successfully fixed this error, by trying to reproduce this in a simpler example: http://stackoverflow.com/a/30672516/4107809

I was making a mistake where multiple instances of a fragment (not the VideoFragment) were added in successive calls to onCreate caused by recreation of the Activity. This fragment addition did not trigger the java.lang.IllegalStateException: Fragment already added because apparently this happens only if you try to add the same fragment instance more than once, not multiple instances of the same fragment.

Upon calling the fragment replace method, the java.lang.IllegalStateException: Fragment already added is generated for the new VideoFragment, even though the VideoFragment is only added once using replace.

By ensuring the different fragment was added only once, the replace by the VideoFragment no longer generates java.lang.IllegalStateException: Fragment already added: VideoFragment, at least for the steps for reproducing I outlined above. The IllegalStateException appears to have nothing to do with adding/replacing the VideoFragment, but with the state of the fragments being replaced.

I am displeased by this resolution for two reasons:

  1. The error message is misleading. It says the VideoFragment has already been added, and I have resolved this by making sure that a different fragment is not added more than once, which did not generate an exception.

  2. The replace documentation is very misleading. Based on my reading, it should not matter what the state of the fragment container is prior to calling to replace; the end state should be determined solely by the fragment that is added from the replace argument. I think this discrepancy is most clear in the linked question, though the answerer in that question disagrees.

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

Comments