0

I'm having an special use case where I need to switch between two fragments. The issue I'm having is that for the second fragment I need to persist it's state, and the only thing that seems to be working for that is to add it to the BackStack.

I rely on the support fragment manager to replace the fragments:

public void toggle() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
    if (fragment instanceof FragmentB && null != fragmentA) {
        // fragment B is visible - we should show fragment A

        getSupportFragmentManager().beginTransaction()
                                   .setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
                                                        R.anim.frag_fade_in, R.anim.frag_fade_out)
                                   .replace(R.id.fragment_container, fragmentA)
                                   .commit();
    } else if (fragment instanceof FragmentA && null != fragmentB) {
        // fragment A is visible - we should show fragment B

         boolean isRestored = false;
         fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
            if (null != fragment) {
                // Restore fragment state from the BackStack
                fragmentB = (FragmentB) fragment;
                isRestored = true;
            }

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction()
                                                                         .setCustomAnimations(R.anim.frag_fade_in,
                                                                                              R.anim.frag_fade_out,
                                                                                              R.anim.frag_fade_in,
                                                                                              R.anim.frag_fade_out);

        transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
        if(!isRestored){
          transaction.addToBackStack(TAG_FRAG_B)
        }
        transaction.commit();
    } else {
        // Just pop any fragments that were added - usually we won't get in here
        getSupportFragmentManager().popBackStack();
    }
}

This in combination with the onBackPressed() override:

@Override
public void onBackPressed() {   
  if (isCurrentFragmentB()) {
      toggle();
  } else {
      // Back key was pressed and we are on fragment A - at this state we simply want to go back to the
      // previous section
      super.onBackPressed();
  }
}

Using this implementation I make sure I reuse fragment B and keep it's state so that it doesn't look like it is created from scratch each time. I also make sure that when I go back, I can go only from fragment B to A and not from fragment A to B.

The issue I encountered is that when super.onBackPressed(); is called and more than one fragment was added(replaced actually, as I want only one active fragment at a time) through the fragment manager, it will throw an exception:

java.lang.IllegalStateException: Fragment already added: FragmentA{af9c26b #0 id=0x7f0e00d3}

This is happening only when the active fragment is FragmentA. I have a suspicion that this is because of the BackStack implementation, but as I've said, I only want the second one to be persisted.

How can I fix this? I am missing something?

Ionut Negru
  • 6,186
  • 4
  • 48
  • 78

1 Answers1

0

I have managed to implement an work-around for this, although it is a little hacky.

Because I need to keep the state of FragmentB, I am forced to add it to the BackStack, but this will actually affect what transition is reversed when onBackPressed() is called.

To avoid this, I had to update the logic for the back press and manually handle that case

@Override
public void onBackPressed() {   
  if (isCurrentFragmentB()) {
      toggle();
  } else if (isCurrentFragmentA()) {
      getSupportFragmentManager().popBackStackImmediate(TAG_FRAG_A, FragmentManager.POP_BACK_STACK_INCLUSIVE);
      // Special case - because we added the fragment B to the BackStack in order to easily resume it's state,
      // this will fail as it will actually try to add fragment A again to the fragment manager (it
      // will try to reverse the last transaction)
      super.finish();
  } else {
      // Usual flow - let the OS decide what to do
      super.onBackPressed();
  }
}

Also, I've optimized the toggle method a little bit:

public void toggle() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);

    @SuppressLint("CommitTransaction") FragmentTransaction transaction =
                getSupportFragmentManager().beginTransaction()
                                           .setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
                                                                R.anim.frag_fade_in, R.anim.frag_fade_out);

    if (fragment instanceof FragmentB && null != fragmentA) {
        // fragment B is visible - we should show fragment A

        fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_A);
        if (null != fragment) {
            // Restore fragment state from the BackStack
            fragmentA = (FragmentA) fragment;
        }

        // Replace current fragment with fragment A and commit the transaction
        transaction.replace(R.id.fragment_container, fragmentA, TAG_FRAG_A).commit();
    } else if (fragment instanceof FragmentA && null != fragmentB) {
        // fragment A is visible - we should show fragment B

         fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
            if (null != fragment) {
                // Restore fragment state from the BackStack
                fragmentB = (FragmentB) fragment;
            }

         // Replace current fragment with fragment B
         transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);

         if (null == fragment) {
              // No entry of the fragment B in the BackStack, we want to add it for future uses
              transaction.addToBackStack(TAG_FRAG_B);
          }

          // Commit the transaction
          transaction.commit();
    } else {
        // Just pop any fragments that were added - usually we won't get in here
        getSupportFragmentManager().popBackStack();
    }
}

I hope this can help others which need an similar flow.

PS: The fragment I want to persist is SupportMapFragment, so that my map isn't always redrawn, re-centered and populated with data every time I want to show it.

Ionut Negru
  • 6,186
  • 4
  • 48
  • 78