Problems with Android Fragment back stack

145,969

Solution 1

Explanation: on what's going on here?

If we keep in mind that .replace() is equal with .remove().add() that we know by the documentation:

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.

then what's happening is like this (I'm adding numbers to the frag to make it more clear):

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(here all misleading stuff starts to happen)

Remember that .addToBackStack() is saving only transaction not the fragment as itself! So now we have frag3 on the layout:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

Possible solution

Consider implementing FragmentManager.BackStackChangedListener to watch for changes in the back stack and apply your logic in onBackStackChanged() methode:

Solution 2

It seems as though fragment [3] is not removed from the view when back is pressed so you have to do it manually!

First of all, don't use replace() but instead use remove and add separately. It seems as though replace() doesn't work properly.

The next part to this is overriding the onKeyDown method and remove the current fragment every time the back button is pressed.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

Solution 3

First of all thanks @Arvis for an eye opening explanation.

I prefer different solution to the accepted answer here for this problem. I don't like messing with overriding back behavior any more than absolutely necessary and when I've tried adding and removing fragments on my own without default back stack poping when back button is pressed I found my self in fragment hell :) If you .add f2 over f1 when you remove it f1 won't call any of callback methods like onResume, onStart etc. and that can be very unfortunate.

Anyhow this is how I do it:

Currently on display is only fragment f1.

f1 -> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

nothing out of the ordinary here. Than in fragment f2 this code takes you to fragment f3.

f2 -> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

I'm not sure by reading docs if this should work, this poping transaction method is said to be asynchronous, and maybe a better way would be to call popBackStackImmediate(). But as far I can tell on my devices it's working flawlessly.

The said alternative would be:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Here there will actually be brief going back to f1 beofre moving on to f3, so a slight glitch there.

This is actually all you have to do, no need to override back stack behavior...

Solution 4

I know it's a old quetion but i got the same problem and fix it like this:

First, Add Fragment1 to BackStack with a name (e.g "Frag1"):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

And then, Whenever you want to go back to Fragment1 (even after adding 10 fragments above it), just call popBackStackImmediate with the name:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Hope it will help someone :)

Solution 5

After @Arvis reply i decided to dig even deeper and I've written a tech article about this here: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

For the lazy developers around. My solution consists in always adding the transactions to the backstack and perform an extra FragmentManager.popBackStackImmediate() when needed (automatically).

The code is very few lines of code and, in my example, I wanted to skip from C to A without jumping back to "B" if the user didn't went deeper in the backstack (ex from C navigates to D).

Hence the code attached would work as follow A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A

where

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

were issued from "B" to "C" as in the question.

Ok,Ok here is the code :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

  fragmentManager.beginTransaction()
      .replace(R.id.content, fragment, tag)
      .addToBackStack(tag)
      .commit();

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}
Share:
145,969
Chris Birch
Author by

Chris Birch

Updated on April 24, 2021

Comments

  • Chris Birch
    Chris Birch about 3 years

    I've got a massive problem with the way the android fragment backstack seems to work and would be most grateful for any help that is offered.

    Imagine you have 3 Fragments

    [1] [2] [3]

    I want the user to be able to navigate [1] > [2] > [3] but on the way back (pressing back button) [3] > [1].

    As I would have imagined this would be accomplished by not calling addToBackStack(..) when creating the transaction that brings fragment [2] into the fragment holder defined in XML.

    The reality of this seems as though that if I dont want [2] to appear again when user presses back button on [3], I must not call addToBackStack in the transaction that shows fragment [3]. This seems completely counter-intuitive (perhaps coming from the iOS world).

    Anyway if i do it this way, when I go from [1] > [2] and press back I arrive back at [1] as expected.

    If I go [1] > [2] > [3] and then press back I jump back to [1] (as expected). Now the strange behavior happens when I try and jump to [2] again from [1]. First of all [3] is briefly displayed before [2] comes into view. If I press back at this point [3] is displayed, and if I press back once again the app exits.

    Can anyone help me to understand whats going on here?


    And here is the layout xml file for my main activity:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical" >
    
    <fragment
            android:id="@+id/headerFragment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            class="com.fragment_test.FragmentControls" >
        <!-- Preview: layout=@layout/details -->
    </fragment>
    <FrameLayout
            android:id="@+id/detailFragment"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
    
            />
    



    Update This is the code I'm using to build by nav heirarchy

        Fragment frag;
        FragmentTransaction transaction;
    
    
        //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
        frag = new Fragment1();
    
        transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.detailFragment, frag);
        transaction.commit();
    
        //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
        frag = new Fragment2();
    
        transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.detailFragment, frag);
        transaction.addToBackStack(null);
        transaction.commit();
    
    
        //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
        frag = new Fragment3();
        transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.detailFragment, frag);
        transaction.commit();
    
    
         //END OF SETUP CODE-------------------------
        //NOW:
        //Press back once and then issue the following code:
        frag = new Fragment2();
        transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.detailFragment, frag);
        transaction.addToBackStack(null);
        transaction.commit();
    
        //Now press back again and you end up at fragment [3] not [1]
    

    Many thanks

  • Chris Birch
    Chris Birch almost 12 years
    Hi jdekei Thanks for your input. Problem is I can't see where I am adding [3] to the backstack. I've added another chunk of code that demonstrates (programmatically) exactly the navigation that i was performing using using buttons.
  • Viktor M.
    Viktor M. over 11 years
    It helps me too but I use only removeCurFragment from you code and a bit another fragment checker. Replace method works for me fine, may be, thats old method issue but now its fine. Thanks
  • Zyoo
    Zyoo almost 11 years
    but the glitch, that's a problem
  • momo
    momo almost 11 years
    Good explanation @arvis. However, how can we prevent this behaviour without resorting to hacky ways like the one from DexterMoon, or the one from Nemanja using popBackStack which shows the Fragment while playing the transition animation?
  • Arvis
    Arvis almost 11 years
    @momo you might implement FragmentManager.BackStackChangedListener to watch for changes to the back stack. Monitor all your transactions with onBackStackChanged() methode and act as necessary: for ex. trace a count of transaction in BackStack; check particular transaction by name (FragmentTransaction addToBackStack (String name)) etc.
  • momo
    momo almost 11 years
    Thanks for answering. I actually tried that earlier today, registered the listener and removed the fragment from onBackstackChange. While the popping transition played the fragment becomes a white empty area. I guess the method is fired when popping starts the animation and not when ends...
  • TharakaNirmana
    TharakaNirmana over 10 years
    this works, but this cannot be used in my project since the fragment gets reloaded! any suggestions?
  • ahaisting
    ahaisting almost 10 years
    This seems less "hack-ey" than listening for BackStack changes. Thanks.
  • stack_ved
    stack_ved almost 10 years
    Avoid using back stacks! it doesn't really help with the overall efficiency! use plain replace() or even better remove/add every time you want to navigate!
  • eliasbagley
    eliasbagley over 8 years
    Great answer. I had to use getSupportFragmentManager().popBackStackImmediate("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); to get it to work for my use case though
  • Nigam Patro
    Nigam Patro almost 8 years
    But, If I am doing this. I am getting blank screen when I am coming back from C to A for Fragment C.
  • Erum
    Erum over 7 years
    @Arvis can u pls help me i m getting same issue .... stack count 0 but still my fragment is visible ?
  • Chris Birch
    Chris Birch over 7 years
    I suspect @Arvis answer should throw some light onto your problem
  • pavel
    pavel over 7 years
    Where i have to put this code???? getSupportFragmentManager().popBackStackImmediate("Frag1", 0); On MainActivty or onBackPress
  • Priyanka
    Priyanka about 6 years
    its not working. :( this is my code ,In my case Fragment is Overlaping . it open the Fragment A, but Frgment A is overlap on the Fragment B .
  • Priyanka
    Priyanka about 6 years
    FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); fragmentManager.popBackStack(FragmentA.class.getName(),Fragm‌​entManager.POP_BACK_‌​STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); FragmentE fragmentE = new FragmentE(); fragmentTransaction.replace(R.id.fragment_content, fragmentE,fragmentE.getClass().getName()); fragmentTransaction.commit();
  • Priyanka
    Priyanka about 6 years
    getting crash on this line fragmentManager.popBackStackImmediate();error: java.lang.IllegalStateException: FragmentManager is already executing transactions at com.example.myapplication.FragmentA$2.onBackStackChanged(Fra‌​gmentA.java:43)