How to resume existing Fragment from BackStack

203,717

Solution 1

Reading the documentation, there is a way to pop the back stack based on either the transaction name or the id provided by commit. Using the name may be easier since it shouldn't require keeping track of a number that may change and reinforces the "unique back stack entry" logic.

Since you want only one back stack entry per Fragment, make the back state name the Fragment's class name (via getClass().getName()). Then when replacing a Fragment, use the popBackStackImmediate() method. If it returns true, it means there is an instance of the Fragment in the back stack. If not, actually execute the Fragment replacement logic.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

EDIT

The problem is - when i launch A and then B, then press back button, B is removed and A is resumed. and pressing again back button should exit the app. But it is showing a blank window and need another press to close it.

This is because the FragmentTransaction is being added to the back stack to ensure that we can pop the fragments on top later. A quick fix for this is overriding onBackPressed() and finishing the Activity if the back stack contains only 1 Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Regarding the duplicate back stack entries, your conditional statement that replaces the fragment if it hasn't been popped is clearly different than what my original code snippet's. What you are doing is adding to the back stack regardless of whether or not the back stack was popped.

Something like this should be closer to what you want:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

The conditional was changed a bit since selecting the same fragment while it was visible also caused duplicate entries.

Implementation:

I highly suggest not taking the the updated replaceFragment() method apart like you did in your code. All the logic is contained in this method and moving parts around may cause problems.

This means you should copy the updated replaceFragment() method into your class then change

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

so it is simply

replaceFragment (fragmentName);

EDIT #2

To update the drawer when the back stack changes, make a method that accepts in a Fragment and compares the class names. If anything matches, change the title and selection. Also add an OnBackStackChangedListener and have it call your update method if there is a valid Fragment.

For example, in the Activity's onCreate(), add

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

And the other method:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Now, whenever the back stack changes, the title and checked position will reflect the visible Fragment.

Solution 2

I think this method my solve your problem:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

which was originally posted in This Question

Solution 3

Step 1: Implement an interface with your activity class

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Step 2: When you call the another fragment add this method:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();

Solution 4

I know this is quite late to answer this question but I resolved this problem by myself and thought worth sharing it with everyone.`

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

And in the onBackPressed()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Share:
203,717

Related videos on Youtube

Kaidul
Author by

Kaidul

I believe one of my answers has brought you on my profile. Since you're here, check some of my other answers which you might find interesting as well :) 2D Segment/Quad Tree Explanation with C++ DFA construction in Knuth-Morris-Pratt algorithm Minimum number of edges to be deleted in a directed graph to remove all the cycles Count number of inversions in an array using segment trees What is the best algorithm to shuffle cards? Two elements of an array whose XOR is maximum efficiently Monolith to Microservices Raising a number to a huge exponent HashMap Space Complexity Best practices for microserviced applications

Updated on April 15, 2021

Comments

  • Kaidul
    Kaidul about 3 years

    I am learning how to use fragments. I have three instances of Fragment that are initialized at the top of the class. I am adding the fragment to an activity like this:

    Declaring and initializing:

    Fragment A = new AFragment();
    Fragment B = new BFragment();
    Fragment C = new CFragment();
    

    Replacing/Adding:

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.replace(R.id.content_frame, A);
    ft.addToBackStack(null);
    ft.commit();
    

    These snippets are working properly. Every fragment is attached to the activity, and is saved to the back stack without any problem.

    So when I launch A, C, and then B, the stack looks like this:

    | |
    |B|
    |C|
    |A|
    ___
    

    And when I press the 'back' button, B is destroyed and C is resumed.

    But, when I launch fragment A a second time, instead of resuming from back stack, it is added at the top of the back stack

    | |
    |A|
    |C|
    |A|
    ___
    

    But I want to resume A and destroy all fragments on top of it (if any). Actually, I just like the default back stack behavior.

    How do I accomplish this?

    Expected: (A should be resumed and top fragments should be destroyed)

    | |
    | |
    | |
    |A|
    ___
    

    Edit: (suggested by A--C)

    This is my trying code:

    private void selectItem(int position) {
            Fragment problemSearch = null, problemStatistics = null;
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction ft = manager.beginTransaction();
            String backStateName = null;
            Fragment fragmentName = null;
            boolean fragmentPopped = false;
            switch (position) {
            case 0:
                fragmentName = profile;
                break;
            case 1:
                fragmentName = submissionStatistics;
                break;
            case 2:
                fragmentName = solvedProblemLevel;
                break;
            case 3:
                fragmentName = latestSubmissions;
                break;
            case 4:
                fragmentName = CPExercise;
                break;
            case 5:
                Bundle bundle = new Bundle();
                bundle.putInt("problem_no", problemNo);
                problemSearch = new ProblemWebView();
                problemSearch.setArguments(bundle);
                fragmentName = problemSearch;
                break;
            case 6:
                fragmentName = rankList;
                break;
            case 7:
                fragmentName = liveSubmissions;
                break;
            case 8:
                Bundle bundles = new Bundle();
                bundles.putInt("problem_no", problemNo);
                problemStatistics = new ProblemStatistics();
                problemStatistics.setArguments(bundles);
                fragmentName = problemStatistics;
            default:
                break;
            }
            backStateName = fragmentName.getClass().getName();
            fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
            if (!fragmentPopped) {
                ft.replace(R.id.content_frame, fragmentName);
            }
            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            ft.addToBackStack(backStateName);
            ft.commit();
    
            // I am using drawer layout
            mDrawerList.setItemChecked(position, true);
            setTitle(title[position]);
            mDrawerLayout.closeDrawer(mDrawerList);
        }
    

    The problem is, when I launch A and then B, then press 'back', B is removed and A is resumed. and pressing 'back' a second time should exit the app. But it is showing a blank window and I have to press back a third time to close it.

    Also, when I launch A, then B, then C, then B again...

    Expected:

    | |
    | |
    |B|
    |A|
    ___
    

    Actual:

    | |
    |B|
    |B|
    |A|
    ___
    

    Should I override onBackPressed() with any customization or am I missing something?

  • Kaidul
    Kaidul almost 11 years
    Thanks for your answer. :) It is working partially. I have edited my question with progress. Please take a look )
  • A--C
    A--C almost 11 years
    @typedef I will make a sample project and see if I can reproduce your issue. I'll edit the question/leave a comment if I find anything useful.
  • A--C
    A--C almost 11 years
    @typedef I edited the question. Make sure to follow all the instructions.
  • Kaidul
    Kaidul almost 11 years
    Hello Sir, this is working perfectly :) MY two problems gone! But in every successful Fragment replacement, I did update the window title by setTitle(title[position]); and changed the selected item of navigation drawer by mDrawerList.setItemChecked(position, true);. This is not working now when I press back button and resume a fragment from backstack. The title and selected item in navigation drawer is not updated. Where I can place this update-code to make the update?
  • Kaidul
    Kaidul almost 11 years
    May be I can update the title and selectedItem of navigation drawer in onResume of every fragment. But this will require to do more codes in every frgament. Is there any good way?
  • A--C
    A--C almost 11 years
    @typedef This edit was the first thing that came to mind. It's definitely a better way than using onResume().
  • Kaidul
    Kaidul almost 11 years
    Sir, one of my fragment is a nested fragment and it has three childs. Problem is - the parent fragment's view is overriding when I am resuming it from backstack. How can I avoid this? DO you encounter something like that before?
  • A--C
    A--C almost 11 years
    @typedef Unfortunately I haven't. Consider making a new question so other people can help as well.
  • Kaidul
    Kaidul almost 11 years
    can I recreate the view of fragment during resume from backstack?
  • A--C
    A--C almost 11 years
    @typedef No, you cannot reinflate the View there. Make a new question, explain the problem in detail, and add any relevant code as well as screenshots.
  • Kaidul
    Kaidul almost 11 years
    this is my new question link: stackoverflow.com/questions/18322020/…
  • Kaidul
    Kaidul almost 11 years
    Sir, one more help please! :( Can I maintain backStack for all fragments except one? I mean I need my one fragment not to include in the backstack. I tried but I can't be able to modify your code to fulfill my purpose. :(
  • Rishabh Srivastava
    Rishabh Srivastava over 10 years
    I want to know that which method of the previous fragment is called first when the current fragment is popped back?Actually I wanted to run a piece of code when the current fragment is paused n previous is opened.
  • A--C
    A--C over 10 years
    @RishabhSrivastava keep in mind a "pop" usually destroys the topmost fragment. As to which method is called first, it depends - have a look at the Fragment Lifecycle. For your case, I'd use an OnBackstackChangedListener so that you can guarantee that you're dealing with the right Fragment.
  • Rishabh Srivastava
    Rishabh Srivastava over 10 years
    i have got two fragments when some data is changed in the first and second is opened,the change is reflected in second and when I change the data in second and go back,i want the change to be reflected in the first,that's what is not happening.I went through fragment life cycle but no help as no method is called when current fragment is popped back.
  • A--C
    A--C over 10 years
    @RishabhSrivastava Which is why I suggested the OnBackstackChangedListener as well.
  • Alexander Farber
    Alexander Farber over 8 years
    Why compare class names - if you can use instanceof instead, on the Fragment returned by findFragmentById?
  • A--C
    A--C over 8 years
    @AlexanderFarber You're correct. Back when I made this answer I wasn't thinking about instanceof :)
  • Zin Win Htet
    Zin Win Htet over 8 years
    Dear Sir, Not Working here.. :'( It's still saying "Fragment already added".
  • albert
    albert over 8 years
  • Leonid Ustenko
    Leonid Ustenko almost 8 years
    Why do you compare Fragment classes names instead of comparing instances like if (frag instanceof A) ... Is it somehow better?
  • Leonid Ustenko
    Leonid Ustenko almost 8 years
    And what to do if in some cases I don't want to add fragment to the back stack? I could overload method like replaceFragment (Fragment fragment, boolean addToBackStack) { ... if (addToBackStack) { ft.addToBackStack(backStateName);} ... } But in this case I woun't be able to update action bar text in onBackStackChanged as it will not be called
  • erluxman
    erluxman over 7 years
    Please let me know if answering like this is against the rules I just wanted to make it easy for people to see in this post. Thanks ..
  • Raeglan
    Raeglan over 6 years
    Your solution sounds good, but if you have three fragments and click on them A-B-C-Band press back one time you'll be going back to A and not back to C. That is because the popBackStackImmediate not only pops the tag given but also everything above. Is there any work around there?
  • Kathir
    Kathir over 5 years
    I am not sure whether it is against the rules (probably not), but this is definitely helpful
  • Rik van Velzen
    Rik van Velzen about 5 years
    What is the use of the third line? -> manager.findFragmentByTag ( tag ); Looks like it isn't doing anything..
  • Vivek Pratap Singh
    Vivek Pratap Singh almost 5 years
    @X-Black...BaseFragment is a base class for every fragment used in the application.
  • July
    July over 4 years
    Please, how can I update some public parameters of the fragments (took from the backstack) before to make it visible? @A--C