56

When using ViewPager with fragments, our onPause, onResume methods are not called when moving between tabs. Is there any way we can figure out in the fragment when we're being made visible or being hidden?

Unfortunately I have logic in onResume, onPause, like registering with location services, that never get stopped when switching tabs because onPause never gets called until one exits the whole app.

Lii
  • 11,553
  • 8
  • 64
  • 88
user291701
  • 38,411
  • 72
  • 187
  • 285
  • 1
    Good simple solution: https://stackoverflow.com/a/35450575/2162226 .. explains how to override for Fragment: `public void setUserVisibleHint(boolean isVisibleToUser)` to know if "current" tab view is showing – Gene Bo May 03 '18 at 19:20

10 Answers10

18

The ViewPager comes with the OnPageChangeListener interface. By setting some flags for the previous and currently shown pages, you can emulate this behavior.

Phix
  • 9,364
  • 4
  • 35
  • 62
  • Could you give a bit more info? – Darkhogg Dec 21 '12 at 15:39
  • 1
    I suppose during OnPageChange() callback, you could set the UserVisiblityHint to true on the new fragment selected and to false on the old fragment. Then calling their respective onResume() and onPause() methods. The user visibility hint then needs to be checked in onResume() to ensure the call is made during onPageChange instead of the standard all during fragment creation. Is there a real flag to recover the documented behavior though? – 3c71 Dec 26 '12 at 02:30
  • Sorry mate, been out of town. Glad someone came through! – Phix Dec 26 '12 at 18:21
  • http://stackoverflow.com/questions/17845641/alternative-for-the-onresume-during-fragment-switching – Yair Kukielka Sep 26 '15 at 17:08
10

Considering previous solutions are not very clear, here is how I solved it thanks to Phix for his hint:

In the OnPageChange() callback:

    Fragment old_fragment = getActiveFragment(old_position);
    Fragment new_fragment = getActiveFragment(new_position);
    old_f.setUserVisibleHint(false);
    old_f.onPause();
    new_f.setUserVisibleHint(true);
    new_f.onResume();

Then in onResume():

    if (getUserVisibleHint())
        /* resume code */

and in onPause():

    if (!getUserVisibleHint())
        /* pause code */

Here is the getActiveFragment code, avoiding to search for that too:

    return fragmentManager.findFragmentByTag("android:switcher:" + viewPagerId + ":" + position);

NB: Fragment have a isVisible() method, but it doesn't seem to be very consistent, hence the use of UserVisibleHint.

EDIT: Replace the UserVisibleHint by a private flag. Worked much better!

3c71
  • 4,313
  • 31
  • 43
  • Sure it's not a good solution, but there's no other at the moment to work-around this OS "bug" or behavior. Why fragments have onResume() onPause() but it's never called in the first place? – 3c71 Dec 17 '14 at 17:18
  • 4
    It is not a OS bug. This means the `Fragment` is still in memory. `ViewPager` will retain in memory at least 2 `Fragments` do to its paging nature. Look at this [answer](http://stackoverflow.com/a/11650582/2029566) for more details. – Emmanuel Dec 17 '14 at 18:06
  • Hence I also said "behavior". Note sure what your comment or link you provide add to the discussion though. The problem is that one need a way to know when the fragment is shown/hidden, which I implemented more or less this way, except I call some private onVisible and onHidden methods. If you know of a better solution, why not write your own? The only 2 answers to this question are more or less the same. – 3c71 Dec 18 '14 at 19:40
  • And it's definitely a bug, if you take a look at Fragment reference (http://developer.android.com/reference/android/app/Fragment.html). basically, onStart() makes the fragment visible to the user, onResume() makes the fragment interacting with the user, onPause() fragment is no longer interacting with the user, onStop() fragment is no longer visible to the user. Those are only called when the fragments are created, which have nothing to do with them being visible. – 3c71 Dec 18 '14 at 19:45
  • 2
    The proper solution would be to perform the logic in the OnPageChangeListener. onPause and onResume are for when the activity moves in and out of the background not for visibility changes. – Dave S Dec 18 '14 at 20:29
8

If you are using Android Support Library (rev 11), you can use getUserVisibleHint or override setUserVisibleHint() to capture the changes visible and hide. Look this post.

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        // do something when visible.
    }
}
Lii
  • 11,553
  • 8
  • 64
  • 88
Evan
  • 266
  • 4
  • 5
6

Current version of ViewPager library allows to enable the desired behavior out of the box, just pass FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT as second argument to FragmentPagerAdapter.

Also consider using ViewPager2 that came to replace ViewPager. ViewPager2 calls onPause() and onResume() as expected by default, no configuration required.

See this answer fore more details.

Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123
4

Solution 1:

Define a SparseArray in your ViewPagers' adapters like below. In this array we'll hold the instance of fragments.

SparseArray<Fragment> registeredFragments = new SparseArray<>();

And Override your Adapters' instantiateItem method.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    registeredFragments.put(position, fragment);
    return fragment;
}

Also Override destroyItem method of your ViewPagers

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    registeredFragments.remove(position);
    super.destroyItem(container, position, object);
}

And define a new method to get your ViewPager Fragments instance.

public Fragment getRegisteredFragment(int position) {
    return registeredFragments.get(position);
} 

Now you can implement your logic with ViewPager's OnPageChangeListener. Here's an example about how you can implement a viewpager's onPageChangeListener:

Define an integer to keep current position:

int currentPos;

yourViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // Empty method
            }

            @Override
            public void onPageSelected(int position) {
                  // This is paused fragment.
                  final Fragment pausedFragment = yourAdapter.getRegisteredFragment(currentPos);

                  // update current position value
                  currentPos = position;

                  // This is resumed fragment
                  final Fragment resumedFragment = yourAdapter.getRegisteredFragment(currentPos);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // Empty method
            }
        });

Finally for restoring instance state update your current position to keep updated in which position user left ViewPager

currentPos = yourViewPager.getCurrentItem();

Solution 2:

For another solution you can find your ViewPager Fragments by Tag. You can generate your tag with:

@NonNull
public static String generateViewPagerFragmentTag(int viewPagerId, int position) {
        final StringBuilder tagBuilder = new StringBuilder();
        tagBuilder.append("android:switcher:");
        tagBuilder.append(viewPagerId);
        tagBuilder.append(":");
        tagBuilder.append(position);
        return tagBuilder.toString();
    }

And find your Fragment:

final Fragment yourFragment = getSupportFragmentManager().findFragmentByTag(generateViewPagerFragmentTag(R.id.your_viewpager, currentPos));

Edit:

A few years ago while i was looking a solution for getting ViewPager's current item I tried userVisibleHint in Fragments and noticed that it was not reliable. I'm not sure if it's reliable or not now but still I avoid to use.

Lii
  • 11,553
  • 8
  • 64
  • 88
savepopulation
  • 11,736
  • 4
  • 55
  • 80
  • 1
    more callbacks introduces more errors, simple use viewpager2 any version above 1.00 .You can simple handle things in onResume when you swipe, since onRes will only be called once you swipe to particular fragment. – shubham chouhan Jan 19 '22 at 13:21
2

You can use below method in Fragment

public void setUserVisibleHint(boolean isVisibleToUser){

}

when isVisibleToUser == true, the fragment is shown

MinnuKaAnae
  • 1,646
  • 3
  • 23
  • 35
tainy
  • 951
  • 7
  • 19
1

After some research I found the perfect solution to this.

when you are initialising your adapter do it like this:-

    import static androidx.fragment.app.FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT;

//(i am using androidx but if you have not yet migrated yet you can use appcompat)

    adapter = new FeedTabsPagerAdapter(getSupportFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);

After adding this line you'll be getting your onResume() and onPause callbacks perfectly. Please mark it correct solution if this issue is solved by my code .Cheers!

Anubhav Malik
  • 242
  • 7
  • 13
1

Use Viewpager2 latest versions, it will call onResume() only when you scroll to that fragment, alike Vp1 which calls it before for +1 next fragment.

shubham chouhan
  • 580
  • 7
  • 8
0

A bit late to the party but in my current app I've set the OffScreenPageLimit to 1 for my viewpager, this means the fragments are not cached and each time the view changes it reloads the current fragment

https://developer.android.com/reference/android/support/v4/view/ViewPager.html#getOffscreenPageLimit()

Phil
  • 1,609
  • 12
  • 24
0

To give a bit more definition to the methods in savepopulation's answer:

yourPager.setOnPageChangeListener(new GridViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int row, int column, float rowOffset, float columnOffset, int rowOffsetPixels, int columnOffsetPixels) {

        }

        @Override
        public void onPageSelected(int row, int column) {
            // When on page selected if position is not specific for you
            // you can find your fragment or you can switch position.
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });
Community
  • 1
  • 1
suomi35
  • 1,069
  • 12
  • 11