13

So, there are some similar questions, but I have not found a single one solving this issue. The official android documentation seems intuitive but when I implement an app with a more complicated workflow, the fragments backstack gets messy and weird stuff starts happening. I developed a skeleton for simple apps, with the idea of a single activity which can be accessed by its fragments to start other fragments. This is how I did it:

1- I let my main activity implement an interface called "FragmentDelegate"

public interface FragmentDelegate {
    public void startFragment(CCFragment fragment, boolean addToBackStack);
}

2- The implementation of the startFrargment method:

@Override
public void startFragment(CCFragment fragment, boolean addToBackStack) {

    FragmentManager fragmentManager = getSupportFragmentManager();

    fragment.setFragmentDelegate(this);
    FragmentTransaction fragmentTransaction = fragmentManager
            .beginTransaction();

    fragmentTransaction.setCustomAnimations(R.anim.slide_in_right,
            R.anim.slide_out_left, R.anim.slide_in_left,
            R.anim.slide_out_right);

    fragmentTransaction.replace(CONTENT_VIEW_ID, fragment,
            "callerFragmentClassName");
    if (addToBackStack)
        fragmentTransaction.addToBackStack("callerFragmentClassName");
    fragmentTransaction.commitAllowingStateLoss();

}

The cool thing about this is, I can call from any fragment:

mFragmentDelegate.startFragment(aNewFragment, addToBackStack);

OK now think of the following case scenario:

I start my activity with an initial fragment, lets say fragment A. From fragment A, I call the Camera Activity for a result. When the result arrives, I start Fragment B (adding A to the backstack). From B I start Fragment C without adding B to the backstack. So we have this in the backstack:

[A] [C]

If I press the back button, I get back to A. If I repeat the process, the backstack gets messed up and when I press back, it takes me to the C fragment again and again...

I know this is difficult to understand (and more difficult for me to explain, because English is not my mother tongue) but if someone could explain to me how do the android fragments backstack really work, or provide some kind of skeleton for an app, would be great.

Sebastian Breit
  • 6,137
  • 1
  • 35
  • 53
  • Are you using findFragmentByTag(...) to get an instance of the Fragments if they are already instantiated? Because it sounds like you have multiple instances of the same fragments. – Steve Benett Apr 21 '14 at 15:15
  • You are right, when I start a fragment I instantiate a new one... but I require that because the fragment might receive arguments and be different. – Sebastian Breit Apr 21 '14 at 15:17
  • You can always set new arguments to the instance of an fragment. – Steve Benett Apr 21 '14 at 15:18
  • Yes but this is not the problem.. the thing is, the backstack does not behave like supposed to. I'd like to understand the behavior instead of finding a workaround for it :) thanks anyways! – Sebastian Breit Apr 21 '14 at 15:25
  • http://stackoverflow.com/questions/14354885/android-fragments-backstack check the answer here and i am sure you will get an idea about how to use fragment back stack – GvSharma Apr 24 '14 at 11:00
  • When does a fragment call startFragment? – stan0 Apr 24 '14 at 11:52

4 Answers4

21

Explanation:

When the FragmentManager reverts saved FragmentTransactions (e.g. the user click the Back button or you called some pop method on it), it executes the opposite of the action that's stored in the saved FragmentTransaction. You can see the exact code here, it's pretty straightforward.

In your case, the saved operation type is 'replace', so reverting that means removing all added Fragment instances and re-adding the removed one. So when you have a fragment stack like this:

[A] [B]

FragmentManager knows, that is has to remove the [B] Fragment instance and add the [A] upon a pop operation. The problem is, that you replaced [B] with [C]:

[A] [C]

Unfortunately, the saved Fragment BackStack entry does not know about [C]. So during a pop operation, the FragmentManager will re-add [A], will not find [B] so it does nothing about that, and leave [C] where it is.

Solution

One possible solution for this problem is using child fragments. Fragment-A is a top-level Fragment, but Fragment-B and Fragment-C are child fragments of a WrapperFragment.

Basically, when you navigate to Fragment-B, you replace Fragment-A with the WrapperFragment in a saved transaction:

[A] [[B]]

Afterwards, when the user navigates to Fragment-C, you only replace the WrapperFragment's [B] with [C]:

[A] [[C]]

When the user presses the Back button, we'll correctly get back to Fragment-A:

[A]

Demo

I've assembled a GitHub project to demonstrate this technique.

Zsombor Erdődy-Nagy
  • 16,864
  • 16
  • 76
  • 101
1

I can't figure out how you ended up with popping C fragments again and again without whole picture of what you did, but here what you got wrong about backstack:

  • BackStack entry actually saves the current state of whole FragmentManager, not just single fragment. I.e. if you perform multiple operations with multiple fragments in single a transaction and call addToBackStack(), you'll save the state of all fragments in the upcoming transaction.

  • When you call addToBackStack() you actually adding the next state to the backstack, i.e. actually, if you've called addToBackStack() when you've added A fragment, you actually have a state with B and a state with A behind it -- [A] [B], not [A] [C].

I recommend you to use this code and try everything with debugger looking at a state of FragmentManager object:

    int count = fragmentManager.getBackStackEntryCount();
    fragmentTransaction.addToBackStack(String.valueOf(count));

This way you'll be able to trace what's really going on in your app.

Dmide
  • 6,422
  • 3
  • 24
  • 31
0

First of all remember:

 .replace() = .remove().add()

some possible solutions

  public void onBackPressed(){
  // here you have to remove your last fragment.
  super.onBackPressed();
}

///////////////////////////////////////////////////////////////////////////////////////////////

@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);

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

transaction.commit();
}

////////////////////////////////////////////////////////////////////////////////////////

for more information about backstack of fragments Check Here and Here

Pankaj Arora
  • 10,224
  • 2
  • 37
  • 59
0

This may not seem as an solution but I think it's better if you don't use fragments in situations like this. At first having only one Activity and replacing fragments on demand seems like a great idea but when you try to build your application this way things get messy. (I've been there and I'm speaking from experience). Managing BackStack yourself is only one of the problems. As you go farther you end up with one activity with so many on related code inside it which is not cool at all!!

I suggest you create one activity for each fragment you've got and simply put your fragment inside it. Your activities still can have multiple fragments in some case (lets say tablets or landscape mode) and one in some other case (like portrait mode) But you don't have to use replace method anymore.

Activities can have parent activity so BackStack problem will be solved in best possible way as many other problems. This is only a suggestion based on experience i had with fragments. Do what you think best.

Alireza Ahmadi
  • 5,122
  • 7
  • 40
  • 67