19

Does the Back Stack support interaction with nested Fragments in Android?

If it does, what am I doing wrong? In my implementation, the back button is completely ignoring the fact that I added this transaction to the back stack. I'm hoping it is not because of an issue with nested fragments and just me doing something incorrectly.

The following code is inside of one of my fragments and is used to swap a new fragment with whatever nested fragment is currently showing:

     MyFragment fragment = new MyFragment();
     FragmentTransaction ft = getChildFragmentManager().beginTransaction();
     ft.setCustomAnimations(R.animator.slide_in_from_right, R.animator.slide_out_left, R.animator.slide_in_from_left, R.animator.slide_out_right);
     ft.addToBackStack(null);
     ft.replace(R.id.myFragmentHolder, fragment);
     ft.commit();
Blo
  • 11,903
  • 5
  • 45
  • 99
MikeS
  • 3,891
  • 6
  • 35
  • 51
  • Anyone seeing this OP can take a look at another one, http://stackoverflow.com/questions/13418436/android-4-2-back-stack-behaviour-with-nested-fragments, which explains better. – lcn Nov 27 '13 at 01:42

4 Answers4

30

I have the same problem, I would like to nest fragments, and to keep a back stack for each nested fragment.

But... it seems that this case is not handled by the v4 support library. In the FragmentActivity code in the library, I can find :

public void onBackPressed() {
    if (!mFragments.popBackStackImmediate()) {
        finish();
    }
}

The mFragments represents the FragmentManager of the activity, but it does not seem this manager "propagates" the pop to children managers. A workaround would be to manually call the popBackStackImmediate() on the child manager, like this in the activity inherited from FragmentActivity :

private Fragment myFragmentContainer;

    @Override
    public void onBackPressed() {
            if (!myFragmentContainer.getChildFragmentManager().popBackStackImmediate()) {
                finish(); //or call the popBackStack on the container if necessary
            }
    }

There might be a better way, and a more automated way, but for my needs it is allright.

la_urre
  • 486
  • 4
  • 6
  • This doesn't seem to be working. Firstly, Fragments don't have a popBackStackImmediate() method (only FragmentManagers do). I tried substituting `this.getFragmentManager().popBackStackImmediate()`, but basically didn't see anything different than what was already happening. – MikeS Dec 10 '12 at 15:32
  • 1
    After working with what you gave me, I have found the solution! I will edit your response to contain the solution and mark it as accepted as you put me most of the way to the right answer... – MikeS Dec 10 '12 at 15:43
  • Oh boy, my comment does not make any sense.... the popBackStackImmediate has to be called on the child fragment manager of the fragment container... Sorry for this s*** ! Of course an "add" has to be made on the child manager as well... – la_urre Dec 11 '12 at 14:35
  • As it seems to me and after some debugging it turns out that when you try to call the childFragmentManager it is null and therefore a new one gets created which has lost its previous state. So the BackStack of the childFragmentManager is empty and the called method will return false. – mitch000001 Jun 14 '13 at 10:43
4

In my current project we have multiple "nested layers" so I've come up with following workaround to automatically pop backstack only for top level fragment managers:

@Override
public void onBackPressed() {
    SparseArray<FragmentManager> managers = new SparseArray<>();
    traverseManagers(getSupportFragmentManager(), managers, 0);
    if (managers.size() > 0) {
        managers.valueAt(managers.size() - 1).popBackStackImmediate();
    } else {
        super.onBackPressed();
    }
}

private void traverseManagers(FragmentManager manager, SparseArray<FragmentManager> managers, int intent) {
    if (manager.getBackStackEntryCount() > 0) {
        managers.put(intent, manager);
    }
    if (manager.getFragments() == null) {
        return;
    }
    for (Fragment fragment : manager.getFragments()) {
        if (fragment != null) traverseManagers(fragment.getChildFragmentManager(), managers, intent + 1);
    }
}
MatrixDev
  • 1,432
  • 15
  • 20
4

As of API 26 there is a setPrimaryNavigationFragment method in FragmentTransaction that can be used to

Set a currently active fragment in this FragmentManager as the primary navigation fragment.

This means that

The primary navigation fragment's child FragmentManager will be called first to process delegated navigation actions such as FragmentManager.popBackStack() if no ID or transaction name is provided to pop to. Navigation operations outside of the fragment system may choose to delegate those actions to the primary navigation fragment as returned by FragmentManager.getPrimaryNavigationFragment().

As mentioned by @la_urre in the accepted answer and as you can see in FragmentActivity's source, the FragmentActivity's onBackPressed would call its FragmentManager's popBackStackImmediate() which in turn would check whether there is an mPrimaryNav (primary navigation, I assume) fragment, get its child fragment manager and pop its backstack.

stan0
  • 11,549
  • 6
  • 42
  • 59
-3
@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        return;
    }
    finish();
}
Agustin Alvarez
  • 349
  • 1
  • 2
  • 11