251

I'm converting an app to use fragments using the compatibility library. Now currently I have a number of activities (A B C D) which chain onto one another, D has a button 'OK' which when pressed calls finish which then bubbles up through onActivityResult() to additionally destroy C and B.

For my pre Honycomb fragment version each activity is effectively a wrapper on fragments Af Bf Cf Df. All activities are launched via startActivityForResult() and onActivityResult() within each of the fragments can happily call getActivity().finish()

The problem that I am having though is in my Honeycomb version I only have one activity, A, and fragments Bf, Cf, Df are loaded using the FragmentManager.

What I don't understand is what to do in Df when 'OK' is pressed in order to remove fragments Df, Cf, and Bf?

I tried having the fragment popping itself off the stack but this resulted in an exception. onActivityResult() is useless because I have not loaded up the fragment using startActivityForResult().

Am I thinking about this completely the wrong way? Should I be implementing some sort of listener that communicates with either the parent fragment or activity in order to do the pop using the transaction manager?

xarlymg89
  • 2,552
  • 2
  • 27
  • 41
PJL
  • 18,735
  • 17
  • 71
  • 68

13 Answers13

325

While it might not be the best approach the closest equivalent I can think of that works is this with the support/compatibility library

getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();

or

getActivity().getFragmentManager().beginTransaction().remove(this).commit();

otherwise.

In addition you can use the backstack and pop it. However keep in mind that the fragment might not be on the backstack (depending on the fragmenttransaction that got it there..) or it might not be the last one that got onto the stack so popping the stack could remove the wrong one...

Manfred Moser
  • 29,539
  • 13
  • 92
  • 123
  • 12
    While this approach works, if you are using addToBackStack(null) it will leave the back button handler +1. So you'll have to press it twice. – user123321 Oct 18 '12 at 01:54
  • So can you avoid that somehow? – Manfred Moser Oct 18 '12 at 07:57
  • 34
    "pop" the fragment from the FragmentManager. – user123321 Oct 25 '12 at 18:34
  • 2
    I have tried the above procedure but it is giving this error "java-lang-illegalstateexception-can-not-perform-this-action-after-onsaveinstance". So where exactly i have to remove the fragment – KK_07k11A0585 Jul 15 '13 at 14:36
  • @ManfredMoser There is a risk associated with this approach: "java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState" – IgorGanapolsky Aug 29 '14 at 18:09
  • You can guard against that in your overall app design and when you cause those transactions. – Manfred Moser Aug 30 '14 at 03:14
  • 6
    This answer is a bad practice and should not be getting up votes. Fragments are not meant to be self-aware like this. It kills reusability, which is the point of fragments! The fragment should signal the activity to remove it through any number of means. The callback interface method is a popular choice. http://developer.android.com/training/basics/fragments/communicating.html – colintheshots Apr 13 '15 at 16:37
  • @colintheshots .. thats a application design choice and completely outside of the scope of the question. – Manfred Moser Apr 13 '15 at 17:27
  • 2
    @ManfredMoser I disagree. This is very much the point of the question. He has an entire sequence of fragments to remove. This code has no null checks or checks for whether the activity is attached. It will break in production because it depends upon too many things a fragment does not know. – colintheshots Apr 13 '15 at 19:00
  • 1
    Yes and no. I have production applications that use this design and they work just fine. But yes... it does require a different sort of pattern. In my case it evolved long before the Android dev team came up with many of the recommendations that exist now and go all to way to teams like Square completely abandoning the use of fragments. – Manfred Moser Apr 13 '15 at 19:36
  • 1
    When using Android Studio Wizard to create a Master/Detail Flow, a "self aware" detail fragment is created. It sets the AppBar title by calling 'getActivity()'. So Google itself is not following its own recommendations? Seems like everyone can use fragments, as they want to... – AlbAtNf Jun 19 '17 at 13:33
  • This is a great versatile answer. It is particularly good for the case where there are many instances of a fragment, and the one to be removed is arbitrary. – Hong Mar 19 '18 at 12:33
281

You can use the approach below, it works fine:

getActivity().getSupportFragmentManager().popBackStack();
Carsten
  • 11,287
  • 7
  • 39
  • 62
Eric Yuan
  • 3,324
  • 1
  • 14
  • 14
  • 47
    This answer is like 10 times better than the accepted one - straight to the point. – nikib3ro Feb 26 '13 at 05:05
  • 53
    It's also 10 times worse with regards to design than the accepted one. A fragment is supposed to be a small "helper" to an activity and should never bei in control over itself or other fragments – avalancha Jun 27 '13 at 14:56
  • 6
    The solution is not correct as @avalancha pointed out. Have a look at http://developer.android.com/guide/components/fragments.html#EventCallbacks – the_dark_destructor Jul 04 '13 at 20:52
  • 2
    I am using this method onActivityResult and getting error "Can not perform this action after onSaveInstanceState". How can I resolve it? – Jayeshkumar Sojitra Mar 14 '14 at 11:09
  • 1
    `popBackStack()` is the only solution which works when you want to set the action bar title back to the previous state after you delete the fragment. Otherwise I wouldn't need to combine the both high rated solutions from http://stackoverflow.com/questions/13472258/handling-actionbar-title-with-the-fragment-back-stack and this solution here to always set the proper action bar title after various use cases. like back pressing, deleting, adding replacing, and so on. – Bevor Mar 15 '16 at 20:27
  • @Bevor Why should the "previous state" matter? Just set the title in onResume() (or if you use a menu in onCreateOptionsMenu()) in each fragment that should change the title. – The incredible Jan Feb 08 '18 at 14:21
  • @TheincredibleJan And what if the fragment is the last fragment: Then the title stays with the latest fragment title, because Activity's `onResume()` is not called. Moreover there are combinations with back button press and so on which one has to take care of. – Bevor Feb 08 '18 at 17:39
  • There can be more than one fragment in the current stack. popping the backstack will pop all of them out which may not be appropriate in many cases. – hopia Jul 09 '19 at 23:15
67

What I don't understand is what to do in Df when 'OK' is pressed in order to remove fragments Df, Cf, and Bf?

Step #1: Have Df tell D "yo! we got the OK click!" via calling a method, either on the activity itself, or on an interface instance supplied by the activity.

Step #2: Have D remove the fragments via FragmentManager.

The hosting activity (D) is the one that knows what other fragments are in the activity (vs. being in other activities). Hence, in-fragment events that might affect the fragment mix should be propagated to the activity, which will make the appropriate orchestration moves.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • But in my Honeycomb version there is no D, that's my difficultly. There is simply an activiy A which loads fragment Bf, which loads Cf, which loads Df using FragmentTransaction. – PJL May 05 '11 at 17:03
  • 1
    @PJL: Sorry, I meant A. This is one reason to use a listener interface, so multiple activities can all respond to the "we got the OK click" event from Df. – CommonsWare May 05 '11 at 17:18
  • 1
    As I'm currently porting I called a listener method from fragments Df's onActivityResult method into the activity whereupon I then called popBackStack on the FragmentManager. However, this results in an exeption "IllegalStateException: Can not perform this action after onSaveInstanceState'. Any ideas as to how I can overcome this? – PJL May 06 '11 at 10:53
  • OK because I'm launching an activity, E, in front my main one (A) then I get a call to A:onSaveInstanceState which means that I can't call popBackStack. What I don't understand then is if A loads Bf, Cf, Df which in turns fires off activiy E which fills the entire screen, then how to destroy the fragment stack? Again perhaps I'm approaching this in the wrong way? – PJL May 06 '11 at 11:29
  • @PJL: You should leave the fragments alone if you are firing off a new activity. That way, when the user returns to the original activity, everything was as the user left it, as the user will probably expect. Frankly, I think your problem is trying to do a wizard-like flow as a series of fragments all simultaneously on the screen. – CommonsWare May 06 '11 at 14:23
  • There's something I do not understand. Imagine that I have on the stack two activities, A and B, with B at the top of the stack. If B contains a fragment and at some point I want to close activity B, ¿should I call `finish()` first or should I call the `remove` method with the *FragmentManager* instance associated with the class B before calling `finish()`? – Diego Palomar Sep 24 '13 at 19:07
  • 1
    @DiegoPalomar: `finish()` should suffice. – CommonsWare Sep 24 '13 at 19:11
  • How would you go about Removing a fragment (that was in back stack) when the phone back button is pressed? This pops the back stack but the fragment becomes in stop state but is not destroyed until the activity finishes? – user3364963 Oct 26 '14 at 23:00
  • @user3364963: I am sorry, but I do not understand your question. I would recommend that you ask a fresh Stack Overflow question where you explain more about your concerns. – CommonsWare Oct 26 '14 at 23:03
  • @CommonsWare, Sorry I wrote it all wrong. I meant to say was if I have a fragment A which is replaced by Fragment B. I add Fragment A to backstack. When the user presses phone back button, the back stack is popped and fragment A is restored. My question, is fragment B completely destroyed after the back stack is popped or do I need to remove it as you mentioned in your answer? – user3364963 Oct 26 '14 at 23:22
  • 1
    @user3364963: It has been a while since I investigated that, but IIRC, it is destroyed when it is popped off the back stack. Add an `onDestroy()` method to your fragment and see if it gets called. – CommonsWare Oct 26 '14 at 23:23
  • @CommonsWare,Yes thanks I put a log statement in, it is called. – user3364963 Oct 26 '14 at 23:31
38

You should let the Activity deal with adding and removing Fragments, as CommonsWare says, use a listener. Here is an example:

public class MyActivity extends FragmentActivity implements SuicidalFragmentListener {

    // onCreate etc

    @Override
    public void onFragmentSuicide(String tag) {
        // Check tag if you do this with more than one fragmen, then:
        getSupportFragmentManager().popBackStack();
    }
}

public interface SuicidalFragmentListener {
    void onFragmentSuicide(String tag);
}

public class MyFragment extends Fragment {

    // onCreateView etc

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
           suicideListener = (SuicidalFragmentListener) activity;
        } catch (ClassCastException e) {
           throw new RuntimeException(getActivity().getClass().getSimpleName() + " must implement the suicide listener to use this fragment", e);
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // Attach the close listener to whatever action on the fragment you want
        addSuicideTouchListener();
    }

    private void addSuicideTouchListener() {
        getView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              suicideListener.onFragmentSuicide(getTag());
            }
        });
    }
}
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • 5
    Suicide much emo style? How about "SelfClosing" or "AutoClose" or "SmartClose"(r) – DritanX Apr 08 '14 at 18:02
  • 22
    it's not closing it's *DYING FOREVER* ;-( – Blundell Apr 08 '14 at 19:34
  • 3
    This is a much cleaner approach than the other answers. The activity creates and presents the fragment, and should control its lifecycle. When something happens that indicates the fragment should no longer be in view, it should tell the Activity that and let the activity remove it. – Christopher Pickslay Apr 28 '14 at 23:09
  • 10
    Technically, if we are to supposed to have the activity kill the fragment, then the fragment isn't suicidal. The activity is homicidal. – Casey Murray Sep 24 '16 at 23:09
19

In the Activity/AppCompatActivity:

@Override
public void onBackPressed() {
    if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        // if you want to handle DrawerLayout
        mDrawerLayout.closeDrawer(GravityCompat.START);
    } else {
        if (getFragmentManager().getBackStackEntryCount() == 0) {
            super.onBackPressed();
        } else {
            getFragmentManager().popBackStack();
        }
    }
}

and then call in the fragment:

getActivity().onBackPressed();

or like stated in other answers, call this in the fragment:

getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
Codeversed
  • 9,287
  • 3
  • 43
  • 42
6

If you are using the new Navigation Component, is simple as

findNavController().popBackStack()

It will do all the FragmentTransaction in behind for you.

Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
4

See if your needs are met by a DialogFragment. DialogFragment has a dismiss() method. Much cleaner in my opinion.

Vasudev
  • 1,936
  • 19
  • 17
3

I create simple method for that

popBackStack(getSupportFragmentManager());

Than place it in my ActivityUtils class

public static void popBackStack(FragmentManager manager){
        FragmentManager.BackStackEntry first = manager.getBackStackEntryAt(0);
        manager.popBackStack(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }

It's work great, have fun!

Eliasz Kubala
  • 3,836
  • 1
  • 23
  • 28
1

If you need to popback from the fourth fragment in the backstack history to the first, use tags!!!

When you add the first fragment you should use something like this:

getFragmentManager.beginTransaction.addToBackStack("A").add(R.id.container, FragmentA).commit() 

or

getFragmentManager.beginTransaction.addToBackStack("A").replace(R.id.container, FragmentA).commit()

And when you want to show Fragments B,C and D you use this:

getFragmentManager.beginTransaction.addToBackStack("B").replace(R.id.container, FragmentB, "B").commit()

and other letters....

To return to Fragment A, just call popBackStack(0, "A"), yes, use the flag that you specified when you add it, and note that it must be the same flag in the command addToBackStack(), not the one used in command replace or add.

You're welcome ;)

xarlymg89
  • 2,552
  • 2
  • 27
  • 41
  • I have tested 'popBackStack(0, "A")' and my app comes back to fragment A, but I want only that fragment would be removed from Back Stack...How can I remove fragment from Stack without showing in screen?? – KryNaC Jun 19 '15 at 11:28
1

OnCreate:

//Add comment fragment
            container = FindViewById<FrameLayout>(Resource.Id.frmAttachPicture);
            mPictureFragment = new fmtAttachPicture();

            var trans = SupportFragmentManager.BeginTransaction();
            trans.Add(container.Id, mPictureFragment, "fmtPicture");
            trans.Show(mPictureFragment); trans.Commit();

This is how I hide the fragment in click event 1

//Close fragment
    var trans = SupportFragmentManager.BeginTransaction();
    trans.Hide(mPictureFragment);
    trans.AddToBackStack(null);
    trans.Commit();

Then Shows it back int event 2

var trans = SupportFragmentManager.BeginTransaction();
            trans.Show(mPictureFragment); trans.Commit();
CodeSi
  • 401
  • 4
  • 6
0

To Close a fragment while inside the same fragment

getActivity().onBackPressed();

kotlin -

requireActivity().onBackPressed()
Dino Sunny
  • 921
  • 1
  • 10
  • 18
-1
parentFragmentManager.apply {
    val f = this@MyFragment
    beginTransaction().hide(f).remove(f).commit()
}
  • 1
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Jun 30 '20 at 20:51
-15

Why not just:

getActivity().finish();

Will
  • 303
  • 3
  • 9