2

From time to time I get the following crash report:

java.lang.IllegalStateException: 
  at androidx.fragment.app.FragmentManagerImpl.addFragment (FragmentManagerImpl.java:1916)
  at androidx.fragment.app.BackStackRecord.executePopOps (BackStackRecord.java:828)
  at androidx.fragment.app.FragmentManagerImpl.executeOps (FragmentManagerImpl.java:2622)
  at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether (FragmentManagerImpl.java:2411)
  at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute (FragmentManagerImpl.java:2366)
  at androidx.fragment.app.FragmentManagerImpl.execPendingActions (FragmentManagerImpl.java:2273)
  at androidx.fragment.app.FragmentManagerImpl$1.run (FragmentManagerImpl.java:733)
  at android.os.Handler.handleCallback (Handler.java:808)
  at android.os.Handler.dispatchMessage (Handler.java:101)
  at android.os.Looper.loop (Looper.java:166)
  at android.app.ActivityThread.main (ActivityThread.java:7529)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:245)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:921)

In line 1916 of FragmentManagerImpl I can find:

throw new IllegalStateException("Fragment already added: " + fragment);

So it says that some fragment is already added. Unfortunately Google does not show the message (which fragment was already added) in the Google Play Console anymore. As far as I understand the Stacktrace this exception occurs when adding a fragment from the backstack right?

I have one FrameLayout in which I add/remove Fragments. I always add them with following code:

public void addFragment(FragmentActivity activity, Fragment fragment) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    String tag = fragment.getClass().getCanonicalName();
    Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(tag);
    if (fragment.isAdded()) {
        return;
    }
    if (prev != null) {
        transaction.remove(prev);
    }
    transaction.replace(R.id.fragment_container, fragment, tag);
    transaction.addToBackStack(null);
    transaction.commit();

And I add DialogFragments with following method:

public void openFragmentDialog(FragmentActivity activity, DialogFragment dialogFragment, String tag) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(tag);
    if (prev != null) {
        transaction.remove(prev);
    }
    transaction.addToBackStack(null);
    dialogFragment.show(transaction, tag);
}

In which situation could it happen that the IllegalStateException occurs? Am I adding/replacing the Fragments/DialogFragments the wrong way? I could never reproduce that error. But I get reports from Android 4.4 - Android 9 and all types of devices and I have no idea where it could happen.

Could it be something with animations or slow devices? Because it only happens occasionally.

L3n95
  • 1,505
  • 3
  • 25
  • 49
  • I still don't understand fragments well enough to answer your question, but I had similar questions that may be helpful to you: https://stackoverflow.com/q/30627498/4107809 https://stackoverflow.com/q/30671291/4107809 Part of my confusion was with replace and part with multiple copies of fragments. – mattm Jul 12 '19 at 15:46

3 Answers3

1

tag need to be unique. If possible replace it with null, otherwise with something unique.

public void addFragment(FragmentActivity activity, Fragment fragment) {
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.fragment_container, fragment);
    transaction.addToBackStack(null);
    transaction.commit();
}
Bertram Gilfoyle
  • 9,899
  • 6
  • 42
  • 67
1

Note, you are adding and removing two different fragments with the same tag -- the tag should then also be different. If the prev fragment is an instance of a different class, why are you using fragment's class name as the tag to find it?

You should add an if statement to check if prev and fragment are not sometimes the same instance (because of same tag).

If the two fragments are the same instance and you still want to remove and then add the same fragment -- you might have to do it in two different transactions to prevent "Fragment already added" exception.

jhavatar
  • 3,236
  • 1
  • 18
  • 11
  • Okay and when they are the same instance (what means, that prev != null) I could do something like `if (prev.isVisible()) return; else // Create new transaction to remove prev first`? So as conclusion I should replace the `transaction.remove(prev)` with "create new transaction and remove `prev`" right? – L3n95 Jul 13 '19 at 08:22
  • I assume the ideal is that `prev` and `fragment` are not the same instance -- this can easily be achieved by using different `tag`s for each, e.g. ```String tag = fragment.getClass().getCanonicalName() + fragment.hashCode();``` You just need to keep track of the unique tag in this case to remove the added fragment later. – jhavatar Jul 13 '19 at 11:35
  • By same instance i mean ```prev == fragment```. If they are the same instance -- then is it even necessary to add `fragment` since it is already added/present. – jhavatar Jul 13 '19 at 11:39
  • If they are the same instance and you still want to remove and then re-add `fragment` then I would recommend using 2 transactions: ```activity.getSupportFragmentManager().beginTransaction().remove(fragment).commit(); activity.getSupportFragmentManager().beginTransaction().add(fragment).commit();``` – jhavatar Jul 13 '19 at 11:50
  • @jhavatar, I think, transactions are asynchronous, so the second can start before the first finishes. So, I use `commitNow()` in the first transaction (and `replace` instead of `add` in the second). Is `fragment` the same in both transactions? – CoolMind Mar 02 '20 at 10:09
  • @CoolMind you could be right about asynchronous -- I just assumed that work gets done on the same thread and work gets done in scheduled order. What do you mean by "Is fragment the same in both transactions?"? My comment example (remove,add) was for the case where `fragment` was the same instance in both transactions. – jhavatar Mar 03 '20 at 10:46
  • @jhavatar, well, ok, I also have the same `fragment` instance. – CoolMind Mar 03 '20 at 10:53
0

Here's what I've done to check for and prevent duplicate fragments:

MyFragment myFragment = (MyFragment) fragmentManager.findFragmentByTag(MY_FRAGMENT_TAG);

if(myFragment != null && myFragment.isVisible()){
    // Return early if the fragment already exists & is visible
    return;

}else if(myFragment == null){
    // Create a new instance of the fragment if none already exist
    myFragment = new MyFragment();
}

// Perform the fragment transaction
fragmentManager.beginTransaction()
    .replace(R.id.content_container, myFragment, MY_FRAGMENT_TAG)
    .addToBackStack(MY_FRAGMENT_TAG) // Pass in the tag if you want to add to the back stack
    .commit();

For Dialog Fragments I just create a new instance and call .show() on it. The dialog should be dismissed through interaction with it:

MyDialog myDialog = new MyDialog();

// I call getChildFragmentManager here b/c I was using this from within a fragment
// but you can get whichever Fragment Manager is appropriate
myDialog.show(getChildFragmentManager(), MY_DIALOG_TAG);
Sammy T
  • 1,924
  • 1
  • 13
  • 20