29

i have a problem that i have been struggling with for the past 2 days.

I am building an app that uses ActionBar, ViewPager & FragmentPagerAdapter. The code for the Activity, Fragments & FragmentPagerAdapter are exactly as the ones stated in the android example on http://developer.android.com/reference/android/support/v4/view/ViewPager.html

The problem i am facing is -- assuming i have only 2 fragments in the viewPager. when switching/swiping between the two, the fragments are not getting updated. onResume does not get called because the viewPager caches a minimum of 1 fragment to either side of the displayed fragment.

I tried using the onTabSelected to detect when a fragment is selected and then start a method from that fragment with the help of an interface (code below).

public void onTabSelected(Tab tab, FragmentTransaction ft) {
    TabInfo tag = (TabInfo)tab.getTag();
    for (int i=0; i<mTabs.size(); i++) {
        if (mTabs.get(i) == tag) {
            mViewPager.setCurrentItem(i);
        }
    }
    ((IStartStop)getItem(tab.getPosition())).Start();
}

However, when the Start method is used a NullPointerException is fired when trying to update a textview. The start method's code is:

public void Start() {
    TextView tv = _view.findViewById(R.id.text);
    tv.setText("test");
}

The exception is thrown at line:

TextView tv = _view.findViewById(R.id.text);

The IStartStop interface is quite simple:

public interface IStartStop {
    public void Start();
    public void Stop();
}

I don't want to use notifyDataSetChanged(); with POSITION_NONE because every time I swipe to a new fragment, it takes a few seconds to load the fragments

At this time, the fragments only include a textview, in the future they will have an animation and so it is important to:

1- Only run an animation when the fragment is selected and not when the fragment next to it is selected (the way ViewPager caches and resumes fragments).

2- Stop the animation when the fragment is no longer selected to avoid wasting device resources.

Yes, i already checked everything available on the internet but nothing seems to work with me.

Thank you very much for your help!

Steve
  • 293
  • 1
  • 3
  • 5

4 Answers4

60

Surprisingly the ViewPager doesn't do this "natively" (among other things). But not all is lost.

First you have to modify your fragments so they only run the animation when you tell them that it's ok and not when they are instantiated. This way you can play with the viewpager offset (default = 3) and have 2-3 fragments preloaded but not animated.

Second step is to create an interface or similar that defines when the "fragment has become visible".

Third step would be to attach a new OnPageScrollListener to your viewpager.

Code follows (in semi-untested-code):

1) Attach the Listener:

mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(final int i, final float v, final int i2) {
            }
            @Override
            public void onPageSelected(final int i) {
                YourFragmentInterface fragment = (YourFragmentInterface) mPagerAdapter.instantiateItem(mViewPager, i);
                if (fragment != null) {
                    fragment.fragmentBecameVisible();
                } 
            }
            @Override
            public void onPageScrollStateChanged(final int i) {
            }
        });

2) This is your Interface:

public interface YourFragmentInterface {
    void fragmentBecameVisible();
}

3) Change your fragments so they implement this:

public class YourLovelyFragment extends Fragment implements YourFragmentInterface {

4) Implement the interface in the fragment

@Override
public void fragmentBecameVisible() {
    // You can do your animation here because we are visible! (make sure onViewCreated has been called too and the Layout has been laid. Source for another question but you get the idea.
}

Where to go from here?

You might want to implement a method/listener to notify the "other" fragments that they are no longer visible (i.e. when one is visible, the others are not). But that may not be needed.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • Hi Martin, your comment actually makes sense. but i need to try it when i am back home. However, if i understand you correctly, your solution suggests the use of `setOffscreenPageLimit(3)` in order to maintain the fragments, no code should be in `onResume()` and call to the animation should be done manually. which is ver similar to what i did in my code: `((IStartStop)getItem(tab.getPosition())).Start();` but the trick to not have anything in onResume might actually work. I will try it and let you know Thanks! – Steve Dec 06 '13 at 12:49
  • 1
    Hi again @Martín, here is what i found out, the NPE was being thrown because i was calling in my code above `Fragment.instantiate(mContext, info.clss.getName(), info.args)`, however your method worked `PagerAdapter.instantiateItem(vViewPager, position);`. Also as in my code above, all methods form `onResume` & `onPause` are removed and are called from the `onPageChanged` event. I can't believe i didn't even suspect `getItem()` to be causing all those, but i guess that's what happens when you work 9 hours per day and spend more than 6 hours at night trying to finish your other work. Thanks! – Steve Dec 06 '13 at 19:36
  • Thanks Martin Marconcini. I spent whole day but couldn't find any better solution. `mPagerAdapter.instantiateItem(mViewPager, i);` this line of code solve my problem. – Zeeshan Nov 02 '14 at 07:03
  • @MartínMarconcini your solutions works fine except for the first fragment at position 0. The animation does not start for the views in fragment at positon 0 at the start. onPageSelected is not called at the start – Raghunandan Mar 23 '15 at 07:45
  • 1
    Well, it's a known _issue_, but the question asks for _when switching/swiping between the two, the fragments are not getting updated_, because the idea is to be notified when it changes, not when it loads for the first time. You could easily get around that tho… there are some other answers here at StackOverflow about that. ;) – Martin Marconcini Mar 24 '15 at 22:39
  • 1
    Thank you Martin! I have spent a day looking for the solution to this issue and your solution works perfectly! – marty331 Nov 03 '15 at 23:23
  • Thank you very much @MartínMarconcini ...It's always a happy thing finding well documented solutions that work perfectly! – Totoro Mar 02 '16 at 16:10
  • @Lila Glad it worked for you! It used to be a common Android pattern (haven't touched Android in six months, not sure if there's something newer these days, I doubt it). ;) Good luck! – Martin Marconcini Mar 02 '16 at 21:47
  • thnxx @MartinMarconcini You saved my life. I was looking for same callback using LiveData but no luck.. – coder_baba Apr 02 '21 at 17:23
4

This will affect the changes from one page to another page in view pager.

OnPageChangeListener  pagechangelistener =new OnPageChangeListener() {

                @Override
                public void onPageSelected(int arg0) {
                    Logger.logMessage("Called first");

                    pageAdapter.notifyDataSetChanged();
                    indicator.setCurrentItem(arg0);
                }

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

                    Logger.logMessage("Called second");

                }

                @Override
                public void onPageScrollStateChanged(int arg0) {

                    Logger.logMessage("Called third");

                }
            };
            myViewPager.setOnPageChangeListener(pagechangelistener);

Use this in your page adapter.

@Override
    public int getItemPosition(Object object) {

        return POSITION_NONE;
    }
Kathi
  • 1,061
  • 1
  • 16
  • 31
Dinesh IT
  • 87
  • 2
2

setOnPageChangeListener has been deprecated. Instead, you should use addOnPageChangeListener.

mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(final int i, final float v, final int i2) {
        }
        @Override
        public void onPageSelected(final int i) {
            YourFragmentInterface fragment = (YourFragmentInterface) mPagerAdapter.instantiateItem(mViewPager, i);
            if (fragment != null) {
                fragment.fragmentBecameVisible();
            } 
        }
        @Override
        public void onPageScrollStateChanged(final int i) {
        }
    });
A. Vin
  • 865
  • 1
  • 6
  • 19
-7

Add this code when you define the Viewpager

mViewPager.setOffscreenPageLimit(0);

It will not save any view of the fragments when you switch to other fragment .. so switching from fragment to another will create the fragment from the start and update everything.

FinalDark
  • 1,748
  • 1
  • 16
  • 26
  • Please don't do that… this more or less defeats the optimizations inside a FragmentPageAdapter. – Martin Marconcini Dec 06 '13 at 01:56
  • 1
    Hi, ever if i want to use it, the `mViewPager.setOffscreenPageLimit(0)` has a default of 1 and will throw and exception if 0 is used. – Steve Dec 06 '13 at 12:48