90

We're suffering from a very strange issue with ViewPager here. We embed lists on each ViewPager page, and trigger notifyDataSetChanged both on the list adapter and the view pager adapter when updating list data.

What we observe is that sometimes, the page does not update its view tree, i.e. remains blank, or sometimes even disappears when paging to it. When paging back and forth a few times, the content will suddenly reappear. It seems as if Android is missing a view update here. I also noticed that when debugging with hierarchy viewer, selecting a view will always make it reappear, apparently because hierarchy viewer forces the selected view to redraw itself.

I could not make this work programmatically though; invalidating the list view, or the entire view pager even, had no effect.

This is with the compatibility-v4_r7 library. I also tried to use the latest revision, since it claims to fix many issues related to view pager, but it made matters even worse (for instance, gestures were broken so that it wouldn't let me page through all pages anymore sometimes.)

Is anyone else running into these issues, too, or do you have an idea of what could be causing this?

mxk
  • 43,056
  • 28
  • 105
  • 132

13 Answers13

57

If the ViewPager is set inside a Fragment with a FragmentPagerAdapter, use getChildFragmentManager() instead of getSupportFragmentManager() as the parameter to initialize your FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Instead of

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
Austyn Mahoney
  • 11,398
  • 8
  • 64
  • 85
eyeslave
  • 630
  • 7
  • 12
46

We finally managed to find a solution. Apparently our implementation suffered of two issues:

  1. our adapter did not remove the view in destroyItem().
  2. we were caching views so that we'd have to inflate our layout just once, and, since we were not removing the view in destroyItem(), we were not adding it in instantiateItem() but just returning the cached view corresponding to the current position.

I haven't looked too deeply in the source code of the ViewPager - and it's not exactly explicit that you have to do that - but the docs says :

destroyItem()
Remove a page for the given position. The adapter is responsible for removing the view from its container, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).

and:

A very simple PagerAdapter may choose to use the page Views themselves as key objects, returning them from instantiateItem(ViewGroup, int) after creation and adding them to the parent ViewGroup. A matching destroyItem(ViewGroup, int, Object) implementation would remove the View from the parent ViewGroup and isViewFromObject(View, Object) could be implemented as return view == object;.

So my conclusion is that ViewPager relies on its underlying adapter to explicitly add/remove its children in instantiateItem()/destroyItem(). That is, if your adapter is a subclass of PagerAdapter, your subclass must implement this logic.

Side note: be aware of this if you use lists inside ViewPager.

Community
  • 1
  • 1
futtetennista
  • 1,876
  • 1
  • 20
  • 33
  • 2
    I'm having the same problem but with FragmentPagerAdapter, which handles onDestroy for me. None of the other solutions here work either. – Greg Ennis Feb 18 '14 at 22:24
  • 14
    What also could be the problem, is that someone is using getFragmentManger instead of GetChildFragmentManager – Boy Nov 16 '14 at 09:21
  • @Boy what is GetChildFragmentManager ? – fire in the hole Jun 17 '15 at 10:58
  • 1
    @Boy Wooooow.... I had pulling my hair out for an entire two days trying to figure out why I couldn't get anything to update. THANK YOU! (they should really, really put a notice on Google's documentation to use the childfragmentmanager, that is entirely not that you need a different manager). – user0721090601 Jun 18 '17 at 05:51
  • @futtetennista I'm doing same.. facing this issue.. can you check.. https://stackoverflow.com/questions/61727835/textview-content-is-getting-trimmerd-after-viewpager-swipe-back – AskQ May 11 '20 at 12:00
23

I had the exact same problem but I actually destroyed the view in destroyItem (I thought). The problem however was that I destroyed it using viewPager.removeViewAt(index); insted of viewPager.removeView((View) object);

Wrong:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

Right:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}
Ringen
  • 271
  • 2
  • 9
  • Thanks for the hint. Been looking into the issue for quite some time. – Moritz Jan 11 '13 at 13:06
  • 2
    This worked for me, but do you know why the first one is a problem? – HannahMitt Jul 01 '14 at 16:13
  • @HannahMitt removeViewAt will remove whatever view is at that particular position within the ViewPager's current list of children, and pages may be added to the ViewPager in any order (so page 0 may actually be in the ViewPager at index 1 or whatever). removeView will iterate through the list of children and remove exactly the object that is specified. –  Oct 07 '15 at 20:55
  • 2
    Not working for me : Can not cast view to fragment. object here is a fragment. – FRK Apr 11 '17 at 19:38
  • viewpager must be static? – Kanagalingam May 03 '19 at 12:05
11

ViewPager tries to do clever stuff around re-using items, but it requires you to return new item positions when things have changed. Try adding this to your PagerAdapter:

public int getItemPosition (Object object) { return POSITION_NONE; }

It basically tells ViewPager that everything has changed (and forces it to re-instantiate everything). That's the only thing I can think of off the top of my head.

Chris Banes
  • 31,763
  • 16
  • 59
  • 50
  • 1
    Yes, I've read about this option in this thread: http://stackoverflow.com/questions/7263291/viewpager-pageradapter-not-updating-the-view?rq=1 -- however, this seems like the sledgehammer approach. Surely there must be a more elegant way? I wonder if it's related to using lists as pager pages? – mxk Jul 31 '12 at 09:34
  • It is a bit of a sledgehammer approach, but you can work back from that. i.e. return a correct value from the method. – Chris Banes Jul 31 '12 at 10:59
  • @ChrisBanes As you said calling `return POSITION_NONE;`, `forces it to re-instantiate everything` but in my case i don't want to **re-instantiate everything**, so would it be possible to remove `view` without clearing the existing stuff ? Please let me know – Ritesh Adulkar Dec 24 '18 at 06:29
  • you are the life savor – mask8 Jan 19 '19 at 03:22
3

Tried too many solutions but unexpectedly viewPager.post() worked

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
  • 2,602
  • 1
  • 21
  • 36
  • 1
    my god. why no one upvoted this? I got weird behaviour when I reenter a fragment and the viewpager inside not render itself, and delaying it a bit like this actually solved the problem! – Fugogugo Mar 12 '20 at 03:00
1

The Android Support Library has a demo Activity that includes a ViewPager with a ListView on every page. You should probably have a look and see what it does.

In Eclipse (with Android Dev Tools r20):

  1. Select New > Android Sample Project
  2. Select your target API level (I suggest the newest available)
  3. Select Support4Demos
  4. Right-click the project and select Android Tools > Add Support Library
  5. Run the app and select Fragment and then Pager

The code for this is in src/com.example.android.supportv4.app/FragmentPagerSupport.java. Good luck!

Sparky
  • 8,437
  • 1
  • 29
  • 41
  • thanks--I'll have a look at that! Perhaps I'll spot something we're getting wrong. – mxk Jul 31 '12 at 10:33
0

I ran into this and had very similar issues. I even asked it on stack overflow.

For me, in the parent of the parent of my view someone subclassed LinearLayout and overrode requestLayout() without calling super.requestLayout(). This prevented onMeasure and onLayout from being called on my ViewPager (although hierarchyviewer manually calls these). Without being measured they'll show up as blank in ViewPager.

So check your containing views. Make sure they subclass from View and don't blindly override requestLayout or anything similar.

Community
  • 1
  • 1
Tim O'Brien
  • 2,538
  • 1
  • 15
  • 13
0

Had the same issue, which is something to do with ListView (because my empty view shows up fine if the list is empty). I just called requestLayout() on the problematic ListView. Now it draws fine!

Oleg Vaskevich
  • 12,444
  • 6
  • 63
  • 80
0

I ran into this same problem when using a ViewPager and FragmentStatePagerAdapter. I tried using a handler with a 3 second delay to call invalidate() and requestLayout() but it didn't work. What did work was resetting the viewPager's background color as follows:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }
Chris Sprague
  • 3,158
  • 33
  • 24
0

I had a problem with the same symptoms, but a different cause that turned out to be a silly mistake on my part. Thought I'd add it here in case it helps anyone.

I had a ViewPager using FragmentStatePagerAdapter which used to have two fragments, but I later added a third. However, I forgot that the default off screen page limit is 1 -- so, when I'd switch to the new third fragment, the first one would get destroyed, then recreated after switching back. The problem was that my activity was in charge of notifying these fragments to initialize their UI state. This happened to work when the activity and fragment lifecycles were the same, but to fix it I had to change the fragments to initialize their own UI during their startup lifecycle. In the end I also wound up changing setOffscreenPageLimit to 2 so that all three fragments were kept alive at all times (safe in this case since they were not very memory intensive).

dfinn
  • 968
  • 1
  • 10
  • 16
-1

I had similar issue. I cache views because I need only 3 views in ViewPager. When I slide forward everything is okay but when I start to slide backward occurs error, it says that "my view already has a parent". The solution is to delete unneeded items manually.

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        int localPos = position % SIZE;
        TouchImageView view;
        if (touchImageViews[localPos] != null) {
            view = touchImageViews[localPos];
        } else {
            view = new TouchImageView(container.getContext());
            view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            touchImageViews[localPos] = view;
        }
        view.setImageDrawable(mDataModel.getPhoto(position));
        Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
        if (view.getParent() == null) {
        ((ViewPager) container).addView(view);
    }
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        //      ((ViewPager) container).removeView((View) view);
        Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
    }

..................

private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];
Roman Nazarevych
  • 7,513
  • 4
  • 62
  • 67
-1

For me the problem was coming back to the activity after the app process was killed. I am using a custom view pager adapter modified from the Android sources.The view pager is embedded directly in the activity.

Calling viewPager.setCurrentItem(position, true);

(with animation) after setting the data and notifyDataSetChanged() seems to work, but if the parameter is set to false it doesn't and the fragment is blank. This is an edge case which may be of help to someone.

Meanman
  • 1,474
  • 20
  • 17
-1

For Kotlin users:

In your fragments; Use childFragmentManager instead of viewPagerAdapter

Boken
  • 4,825
  • 10
  • 32
  • 42