105

I'm using the compatibility package to use Fragments with Android 2.2. When using fragments, and adding transitions between them to the backstack, I'd like to achieve the same behavior of onResume of an activity, i.e., whenever a fragment is brought to "foreground" (visible to the user) after poping out of the backstack, I'd like some kind of callback to be activated within the fragment (to perform certain changes on a shared UI resource, for instance).

I saw that there is no built in callback within the fragment framework. is there s a good practice in order to achieve this?

JJD
  • 50,076
  • 60
  • 203
  • 339
oriharel
  • 10,418
  • 14
  • 48
  • 57
  • 13
    Great question. I am trying to change the actionbar title depending on what fragment is visible as a use case for this scenarion and it seems like a missing callback in the API to me. – Manfred Moser Jan 05 '12 at 21:54
  • 3
    Your activity can set the title given that it knows which fragments are being displayed at any one time. Also I suppose you could do something in `onCreateView` which will get called for the fragment after a pop. – PJL Jan 31 '12 at 21:48
  • 1
    @PJL +1 According to the reference that is the way it should be done. However I prefer the listener way – momo Aug 04 '13 at 10:38

12 Answers12

117

For a lack of a better solution, I got this working for me: Assume I have 1 activity (MyActivity) and few fragments that replaces each other (only one is visible at a time).

In MyActivity, add this listener:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(As you can see I'm using the compatibility package).

getListener implementation:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume() will be called after a "Back" is pressed. few caveats though:

  1. It assumes you added all transactions to the backstack (using FragmentTransaction.addToBackStack())
  2. It will be activated upon each stack change (you can store other stuff in the back stack such as animation) so you might get multiple calls for the same instance of fragment.
Vishal Chhodwani
  • 2,567
  • 5
  • 27
  • 40
oriharel
  • 10,418
  • 14
  • 48
  • 57
  • 3
    You should accept your own answer as correct if it worked for you. – powerj1984 Dec 06 '11 at 18:05
  • 7
    How does that work in terms of the fragment id you are querying by? How is it different for each fragment and the right one is found in the back stack? – Manfred Moser Jan 05 '12 at 22:20
  • Wouldn't it be easier to just use onResume() inside the fragment? – Warpzit Jan 26 '12 at 11:09
  • How is it that Google still hasn't added this basic feature? I tried everything, but it seems that both "add" and "replace" on fragments are badly broken - they *sometimes* trigger the things Google says they do, but *usually* do not. – Adam May 22 '13 at 12:04
  • 2
    @Warpzit that method is not called, and android docs quietly admit that it will never be called (it's only called when the activity is resumed - which is never, if that activity is already active) – Adam May 22 '13 at 12:05
  • `onResume` is not called. Adding a stack listener like this is the best solution I know. – Ken Jun 05 '13 at 08:24
  • Currently this is the best (only) solution – momo Aug 04 '13 at 10:37
  • 2
    Ridiculous that we have to do this! But glad someone else was able to find this "feature" – stevebot Apr 04 '14 at 20:19
  • 1
    OnResume() is Called.But I am Getting Blank Screen..Any other way to resolve this issue? – kavie Oct 06 '14 at 11:58
  • 3
    onResume() is NOT called – Marty Miller Dec 09 '14 at 10:12
  • 1
    On resume is called every time a fragment is set into a container. However, it is NOT called when you are popping the backstack and the fragment is displayed again. The only solution I've seen work is listening to the back stack listener, as posted in this answer. – Lo-Tan May 22 '15 at 05:12
  • Superb solution! working exact in the way we want.. Thanks! – Meet Vora Jun 08 '16 at 13:38
  • 1
    `onFragmentResume` doesn't exist, use `onResume`. – CoolMind Oct 12 '16 at 11:30
  • r u serious about this answer, is this your work around? – parvez rafi May 31 '17 at 08:53
  • In this method, onResume() will be called twice while current Fragment is added in the backstack. One after onCreate (by lifeccycle) another one from OnBackstackChangeListener(). – Amit Jan 13 '20 at 12:44
33

I've changed the suggested solution a little bit. Works better for me like that:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
zyamys
  • 1,609
  • 1
  • 21
  • 23
Brotoo25
  • 373
  • 3
  • 3
  • 1
    please explain the difference, and why use this. – Math chiller Sep 11 '13 at 23:27
  • 2
    The difference between my solution and the previously one is that on every fragment change like adding, replacing or going backwards in the fragments pile, the "onResume" method of the top fragment will be called. There's no fragment id hardcoded in this method. Basically it calls onResume() in the fragment you're looking at on every change, no matter if it was already loaded in the memory or not. – Brotoo25 Dec 03 '13 at 17:38
  • 9
    There's no record for a method called `getFragments()` in SupportFragmentManager... -1 :-/ – Protostome Apr 07 '14 at 15:40
  • 1
    OnResume() is Called.But I am Getting Blank Screen..Any other way to resolve this issue? – kavie Oct 06 '14 at 11:59
  • I think this leads to a ArrayOutOfBounds Exception if backStackEntryCount is 0. Am I mistaken? Tried to edit the post but it was rejected. – Mike T Mar 10 '16 at 08:04
  • For me this solution sometimes lead to ArrayOutOfBounds Exception. Look at my answer below to catch specific Fragment only. – Quan Nguyen Mar 17 '16 at 11:38
  • Though I rewrote this method for myself, thanks for OnBackStackChangedListener. – CoolMind Aug 02 '16 at 15:01
  • @Protostome you have to have API level set to 26 for that call to work – Maurizio Dec 05 '17 at 05:15
5

After a popStackBack() you can use the following callback : onHiddenChanged(boolean hidden) within your fragment

user2230304
  • 578
  • 1
  • 5
  • 14
4

If a fragment is put on backstack, Android simply destroys its view. The fragment instance itself is not killed. A simple way to start should to to listen to the onViewCreated event, an put you "onResume()" logic there.

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
Franjo
  • 57
  • 1
4

The following section at Android Developers describes a communication mechanism Creating event callbacks to the activity. To quote a line from it:

A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.

Edit: The fragment has an onStart(...) which is invoked when the fragment is visible to the user. Similarly an onResume(...) when visible and actively running. These are tied to their activity counterparts. In short: use onResume()

albodelu
  • 7,931
  • 7
  • 41
  • 84
PJL
  • 18,735
  • 17
  • 71
  • 68
  • 1
    I'm just looking for a method in the Fragment class that is activated whenever it is shown to the user. whether because of a FragmentTransaction.add()/replace(), or a FragmentManager.popBackStack(). – oriharel Jun 28 '11 at 08:15
  • @oriharel See updated answer. When the activity is loaded you'll get various calls, onAttach, onCreate, onActivityCreated, onStart, onResume. If you have called `setRetainInstance(true)' then you won't get an onCreate when the fragment is being reshown on clicking back. – PJL Jun 28 '11 at 09:24
  • 16
    onStart() is never called when clicking "Back" between fragments in the same activity. I tried most of the callbacks in the documentation and none of them are called in such scenario. see my answer for a workaround. – oriharel Jun 28 '11 at 10:21
  • hmm with compat lib v4 I am seeing an onResume for my fragment. I do like @oriharel's answer though as it does distinguish between becoming visible through a popBackStack rather than just being brought to the foreground. – PJL Jan 31 '12 at 21:44
3

In my activity onCreate()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Use this method to catch specific Fragment and call onResume()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
Quan Nguyen
  • 5,074
  • 3
  • 24
  • 26
2

A little improved and wrapped into a manager solution.

Things to keep in mind. FragmentManager is not a singleton, it manages only Fragments within Activity, so in every activity it will be new. Also, this solution so far doesn't take ViewPager into account that calls setUserVisibleHint() method helping to control visiblity of Fragments.

Feel free to use following classes when dealing with this issue (uses Dagger2 injection). Call in Activity:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
AAverin
  • 3,014
  • 3
  • 27
  • 32
0

This is the correct answer you can call onResume() providing the fragment is attached to the activity. Alternatively you can use onAttach and onDetach

iamlukeyb
  • 6,487
  • 12
  • 29
  • 40
0

onResume() for the fragment works fine...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
Rajesh
  • 10,318
  • 16
  • 44
  • 64
Roshan Poudyal
  • 652
  • 7
  • 13
0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

and final extent your Frament from RootFragment to see effect

0

My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
Mahendran Candy
  • 1,114
  • 17
  • 17
0

I have used enum FragmentTags to define all my fragment classes.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

pass FragmentTags.TAG_FOR_FRAGMENT_A.name() as fragment tag.

and now on

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
Ali
  • 2,427
  • 22
  • 25