Android Reorder Fragment Backstack
Asked Answered
A

2

21

I have a number of pages/fragments listed in my navigation drawer, the user is likely to switch between these frequently and I want them in the backstack so that they can navigate back, but I only want one instance of each fragment in the backstack so that the user doesn't not have to press back an insane number of times to exit the app. I can't figure out how to effectively 'reorder' the backstack' without pages getting removed.

Currently when I change page I was using this code to change the fragment and make sure it's only in the back stack once

  if (mFragMgr == null) {
      mFragMgr = getSupportFragmentManager();
  }

  String backStateName = fragmentDescriptor.name();
  boolean fragmentPopped = mFragMgr.popBackStackImmediate(backStateName, 0);
  if (!fragmentPopped){
      mFragMgr.beginTransaction()
      .remove((Fragment) mFragment)
      .replace(R.id.content_frame, (Fragment) mFragment)
      .addToBackStack(backStateName)
      .commit();
  }

I use this code in onBackPressed

  @Override
  public void onBackPressed() {
      if (mFragMgr.getBackStackEntryCount() > 0) {
          mFragMgr.popBackStackImmediate();
      } else {
          super.onBackPressed();
      }
  }

This works but it means it removes pages I don't want removed. Example:

When my user visits 6 pages in the order A > B > C > D > E > C because I'm doing a remove I expected the following stack:

                            [E]    [C]
                     [D]    [D]    [E]
              [C]    [C]    [C]    [D]
       [B]    [B]    [B]    [B]    [B]
[A] -> [A] -> [A] -> [A] -> [A] -> [A]

But what I actually get is the following - it pops everything up to the element that matches the name, this is regardless of whether I include the ".remove((Fragment) mFragment)" or not - (I've already realised now that remove isn't affecting the backstack, so no need to point that out):

                            [E]
                     [D]    [D]
              [C]    [C]    [C]    [C]
       [B]    [B]    [B]    [B]    [B]
[A] -> [A] -> [A] -> [A] -> [A] -> [A]

If I don't use a name and instead use null when adding to the backstack I get the following:

                                   [C]
                            [E]    [E]   
                     [D]    [D]    [D]    
              [C]    [C]    [C]    [C]   
       [B]    [B]    [B]    [B]    [B]  
[A] -> [A] -> [A] -> [A] -> [A] -> [A] 

How can I get the behaviour I expect? Is it possible at all or am I going to need to record the changes myself and skip the backstack altogether?

Appetizer answered 9/2, 2014 at 21:33 Comment(7)
I'm trying to find a valid way to do this, I put some effort into my question - It would be nice if the person downvoting would have the courage to say whyAppetizer
Sorry, there is no method in the FragmentManager API for removing a Fragment state from the middle of the back stack. The API only exposes a standard stack mechanism for pushing and popping.Crum
I thought that might be the case. But my use case seems like a pretty valid, fairly common requirement, and you can achieve similar with activities. I hoped somebody might know of a helper class or something to achieve thisAppetizer
Well, I am not aware of any existing solution for this, nor can one be implemented short of using reflection or a custom FragmentManager implementation. If you are willing to go to these lengths, then it should actually be fairly simple to implement.Crum
If only my bounty didn't have to be paid upfront to confirm this :(Appetizer
I suppose it should be possible to create your own custom Fragment back stack by hand, with whatever functionality you need. You won't be able to make use of the BackStackRecord class, as it depends on the stack model; it will have to be a custom solution. If you are interested, then I might write up an answer based on this in a few days when I have the time (or maybe someone else can). Or you can try doing it yourself if you like - it shouldn't be too difficult.Crum
Thanks, I know how to do this don't worry, I just wanted to avoid it and stick to using built in functionalityAppetizer
B
23

There is no API to do this, but becuse I was wondering the same thing last week,
I took it as an exercise an implemented it myself.

My method allows you to remove a Fragment from anywhere in the backstack and this is achieved by using refrection to modify the internal variables that store the backstack information in FragmentManagerImpl and BackStackRecord.

There are few important variables:

  • mBackStack - stores the BackStackRecords that hold the Fragment info, the previous ones, animations, etc
  • mActive - all added Fragments
  • mAvailBackStackIndices - stores the indices that can be used by the newly inserted records, i.e. indices of null values in mActive
  • and other

Tested with 150+ Fragments and couldn't notice any leaks, but only monitored the heap in DDMS and did not perform any other memory analysis.
So because someting may be broken, explore the code, re-test it, see if anyone provides a better answer, and rethink if you really need to do this in your project.

I uploaded the code as a gist on GitHub, hope it helps.

Bermejo answered 2/3, 2014 at 9:16 Comment(2)
An interesting approach, thanks. It takes a lot of code to get it working, I'll take a look at it in-depth later in the week when I have more timeAppetizer
As I said before, a little too much code for my liking, however, I'm sure this will be the right solution for some visitors and it's taken you some effort to put together so I'll award the bounty to you!Appetizer
L
0

When you want to reuse the fragment before it, just first hide all the fragments and show the desired fragment at a time. By doing this you can reuse your fragments without removing them and without reloading the fragments. But be sure before to implement it you have to add the fragment to backstack.

Litalitany answered 29/7, 2014 at 7:36 Comment(1)
and if you have 2000 fragments you end up OOM.Autunite

© 2022 - 2024 — McMap. All rights reserved.