View pager and fragment lifecycle
Asked Answered
D

8

46

I have a ViewPager that loads three pages at a time. If I swipe from page 1 to page 2 then to 3, the first page(fragment) goes to onPause(). Then, if I swipe to the second page, 1st page comes to onResume() even though the page 1 is still not visible to the user. So, my question is: how to distinguish between the first and second page in code? For example, if I have to run a piece of code when the fragment is visible, how is that done?

Drought answered 11/7, 2014 at 12:38 Comment(1)
check out my answer here #21915308Womanize
C
50

The FragmentPagerAdapter keeps additional fragments, besides the one shown, in resumed state. The solution is to implement a custom OnPageChangeListener and create a new method for when the fragment is shown.

1) Create LifecycleManager Interface The interface will have two methods and each ViewPager’s Fragment will implement it. These methods Are as follows:

public interface FragmentLifecycle {

    public void onPauseFragment();
    public void onResumeFragment();

}

2) Let each Fragment implement the interface Add iplements statement for each class declaration:

public class FragmentBlue extends Fragment implements FragmentLifecycle
public class FragmentGreen extends Fragment implements FragmentLifecycle
public class FragmentPink extends Fragment implements FragmentLifecycle

3) Implement interface methods in each fragment In order to check that it really works as expected, I will just log the method call and show Toast:

@Override
public void onPauseFragment() {
    Log.i(TAG, "onPauseFragment()");
    Toast.makeText(getActivity(), "onPauseFragment():" + TAG, Toast.LENGTH_SHORT).show(); 
}

@Override
public void onResumeFragment() {
    Log.i(TAG, "onResumeFragment()");
    Toast.makeText(getActivity(), "onResumeFragment():" + TAG, Toast.LENGTH_SHORT).show(); 
}

4) Call interface methods on ViewPager page change You can set OnPageChangeListener on ViewPager and get callback each time when ViewPager shows another page:

pager.setOnPageChangeListener(pageChangeListener);

5) Implement OnPageChangeListener to call your custom Lifecycle methods

Listener knows the new position and can call the interface method on new Fragment with the help of PagerAdapter. I can here call onResumeFragment() for new fragment and onPauseFragment() on the current one.

I need to store also the current fragment’s position (initially the current position is equal to 0), since I don’t know whether the user scrolled from left to right or from right to left. See what I mean in code:

private OnPageChangeListener pageChangeListener = new OnPageChangeListener() {

    int currentPosition = 0;

    @Override
    public void onPageSelected(int newPosition) {

        FragmentLifecycle fragmentToShow = (FragmentLifecycle)pageAdapter.getItem(newPosition);
        fragmentToShow.onResumeFragment();

        FragmentLifecycle fragmentToHide = (FragmentLifecycle)pageAdapter.getItem(currentPosition);
        fragmentToHide.onPauseFragment();

        currentPosition = newPosition;
    }

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) { }

    public void onPageScrollStateChanged(int arg0) { }
};

I didn't write the code. Full tutorial here: http://looksok.wordpress.com/2013/11/02/viewpager-with-detailed-fragment-lifecycle-onresumefragment-including-source-code/

Crystacrystal answered 27/10, 2015 at 8:43 Comment(6)
getActivity() in onResumeFragment might return Null(atleast in my case). Solution might be to pass the context(or activity) via the interface. E.g. public void onResumeFragment(Activity activity)Airman
In my case 'getItem();' returned fragment without views. I have solved it by using 'instantiateItem'. developer.android.com/reference/android/support/v4/view/…, int)Laughing
Example: alexandroid.net/android/fragments/…Laughing
not working. most adapters' getItem(pos) return new instance of fragment, but we need cached added to fragment manager fragment. so you'll call .onPauseFragment() on freshly created not attached to activity instance. don't copy-paste untested code. and why the hell it is acceptedQuincuncial
getActivity return null that is because as @Quincuncial told most adapters' getItem(pos) return new instance...If you cache that is notObserve
The fragment returned by onPageSelected(currentPosition) will not be the same fragment that WAS visible. It will be a totally new Fragment on which onCreateView has not yet been called and won't be called until you return to that position. If you want to work with the Fragment that is no longer visible, cache it but make sure to nullify it when you no longer need it.Oxysalt
B
11

if your Fragment extend android.support.v4.app.Fragment

you can use this, it works for me.

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (!isVisibleToUser) {
        //do sth..
    }
}
Blagoveshchensk answered 21/12, 2016 at 6:28 Comment(2)
when the fragment is first created, inside this method, getActivity() or getContext() will return null. I personally prefer the onPageSelected() approach more.Yoko
setUserVisibleHint is deprecated now. is there any alternative for itBeare
F
6

user method pager.setOffscreenPageLimit(numbr) to set how many fragments you want in stack to be hold.

Freewheeling answered 11/7, 2014 at 12:49 Comment(2)
The default value is 1. Which means the previous and next fragment are kept in idle state.Womanize
view pager always has a minimum of 3 pages loaded at a time.and my question is how do i get a callback when one of the pages becomes visible.Drought
P
1

Override setUserVisibleHint(). This method will call once the fragment is visible to the user.

Plaque answered 21/12, 2016 at 6:31 Comment(0)
F
1

Override setUserVisibleHint() this will call when fragment is visible to the user

Fulkerson answered 19/1, 2018 at 10:9 Comment(3)
will also be called when fragment is first createdGorgon
yes..But if you have multple fragments in a viewpager that method would be usefulFulkerson
You just need to test if (getUserVisibleHint) Cuz this function returns weather its visible or not. Make sure u call it after super method.Gorgon
E
0

Solve your problem:

public class FragmentVisibleHelper implements LifecycleObserver {

    private static final String TAG = "VipVisibleHelper";

    public interface IVisibleListener {

        void onVisible();

        void onInVisible();
    }

    boolean mIsVisibleToUser;
    boolean mStarted = false;

    volatile boolean mIsCalledVisible = false;
    volatile boolean mIsCalledInvisible = false;


    IVisibleListener mListener;

    public void setListener(IVisibleListener mListener) {
        this.mListener = mListener;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onResume() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "onResume() called:"));
        if (mIsVisibleToUser) {
            dispatchVisible();
        }
    }

    private void dispatchVisible() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "dispatchVisible() called mIsCalledVisible = [" + mIsCalledVisible + "] mIsCalledInvisible = [" + mIsCalledInvisible + "] "));
        if (!mIsCalledVisible) {
            mIsCalledVisible = true;
            mIsCalledInvisible = false;

            if (Profile.LOG) {
                Log.d(TAG, String.format("%-60s %s", this.toString(), "dispatchVisible() called onVisible"));
            }

            if (mListener != null) {
                mListener.onVisible();
            }
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    void onPause() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "onPause() called:"));
        if (mIsVisibleToUser) {
            dispatchInvisible();
        }
    }

    private void dispatchInvisible() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "dispatchInvisible() called mIsCalledVisible = [" + mIsCalledVisible + "] mIsCalledInvisible = [" + mIsCalledInvisible + "] "));
        if (!mIsCalledInvisible) {
            mIsCalledInvisible = true;
            mIsCalledVisible = false;

            if (Profile.LOG) {
                Log.d(TAG, String.format("%-60s %s", this.toString(), "dispatchInvisible() called onInVisible"));
            }

            if (mListener != null) {
                mListener.onInVisible();
            }
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void onStart() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "onStart() called"));
        mStarted = true;
        if (mIsVisibleToUser) {
            dispatchVisible();
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void onStop() {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "onStop() called"));
        if (mIsVisibleToUser) {
            dispatchInvisible();
        }
        mStarted = false;
    }

    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.d(TAG, String.format("%-60s %s", this.toString(), "setUserVisibleHint() called with: isVisibleToUser = [" + isVisibleToUser + "]:"));
        mIsVisibleToUser = isVisibleToUser;
        if (mStarted) { // fragment have created
            if (mIsVisibleToUser) {
                dispatchVisible();
            } else {
                dispatchInvisible();
            }
        }
    }

    public boolean isVisibleToUser() {
        return mIsVisibleToUser;
    }
}
Empery answered 23/10, 2018 at 3:48 Comment(0)
S
0

The fragment's lifecycle methods are not called when swiping between fragments. You can use ViewPager.SimpleOnPageChangeListener to solve this problem. The sample code is as bellow (in Kotlin).

// other code
mViewPager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() {
    override fun onPageSelected(position: Int) {
        val oldPosition = mViewPager.currentItem
        val oldFragment = mViewPager.adapter?.instantiateItem(mViewPager, oldPosition)
        oldFragment.onPauseStuff() // Hint: do as here call onPause
        val newFragment = mViewPager.adapter?.instantiateItem(mViewPager, position)
        newFragment.onResumeStuff() // Hint: do as here call onResume
}
// other code
Schnabel answered 2/3, 2020 at 6:25 Comment(0)
H
0

Inside pager adapter you can put this method.

    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Fragment fragment = fragments.get(position);
        if (fragment == null) {
            fragment = (Fragment) super.instantiateItem(container, position);
            fragments.put(position, fragment);
        }
        return fragment;
    }

    public Fragment getFragment(int position) {
        return fragments.get(position);
    }

Then inside page change listener you can get current fragment and you can run your method inside your fragment.

currentFragment = adapter.getFragment(viewPager.getCurrentItem());
((DetailNewsFragment)currentFragment).runYourMethod();

Special don`t use adapter.getItem(position). because its return new instance of a fragment.

Hereto answered 27/10, 2022 at 9:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.