Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewPager PagerAdapter not updating the View

I'm using the ViewPager from the compatibility library. I have succussfully got it displaying several views which I can page through.

However, I'm having a hard time figuring out how to update the ViewPager with a new set of Views.

I've tried all sorts of things like calling mAdapter.notifyDataSetChanged(), mViewPager.invalidate() even creating a brand new adapter each time I want to use a new List of data.

Nothing has helped, the textviews remain unchanged from the original data.

Update: I made a little test project and I've almost been able to update the views. I'll paste the class below.

What doesn't appear to update however is the 2nd view, the 'B' remains, it should display 'Y' after pressing the update button.

public class ViewPagerBugActivity extends Activity {      private ViewPager myViewPager;     private List<String> data;      @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.main);          data = new ArrayList<String>();         data.add("A");         data.add("B");         data.add("C");          myViewPager = (ViewPager) findViewById(R.id.my_view_pager);         myViewPager.setAdapter(new MyViewPagerAdapter(this, data));          Button updateButton = (Button) findViewById(R.id.update_button);         updateButton.setOnClickListener(new OnClickListener() {              @Override             public void onClick(View v) {                 updateViewPager();             }         });     }      private void updateViewPager() {         data.clear();         data.add("X");         data.add("Y");         data.add("Z");         myViewPager.getAdapter().notifyDataSetChanged();     }      private class MyViewPagerAdapter extends PagerAdapter {          private List<String> data;         private Context ctx;          public MyViewPagerAdapter(Context ctx, List<String> data) {             this.ctx = ctx;             this.data = data;         }          @Override         public int getCount() {             return data.size();         }          @Override         public Object instantiateItem(View collection, int position) {             TextView view = new TextView(ctx);             view.setText(data.get(position));             ((ViewPager)collection).addView(view);             return view;         }          @Override         public void destroyItem(View collection, int position, Object view) {              ((ViewPager) collection).removeView((View) view);         }          @Override         public boolean isViewFromObject(View view, Object object) {             return view == object;         }          @Override         public Parcelable saveState() {             return null;         }          @Override         public void restoreState(Parcelable arg0, ClassLoader arg1) {         }          @Override         public void startUpdate(View arg0) {         }          @Override         public void finishUpdate(View arg0) {         }     } } 
like image 310
C0deAttack Avatar asked Aug 31 '11 20:08

C0deAttack


People also ask

How do I update my Android ViewPager adapter?

Firstly set viewpager adapter to null then recreate the adapter and set i to it to viewpager. Show activity on this post. Define which message which is needed to update. Write below code in the Fragment which is needed update.

How do I refresh FragmentPagerAdapter?

You can use startActivityForResult(or broadcast/receiver) to get the result from the activity. Then use adapter. notifyDataSetChanged to refresh the fragment.

Is PagerAdapter deprecated?

Technically, ViewPager is not deprecated, but the two concrete PagerAdapter implementations — FragmentPagerAdapter and FragmentStatePagerAdapter — are deprecated.


2 Answers

There are several ways to achieve this.

The first option is easier, but bit more inefficient.

Override getItemPosition in your PagerAdapter like this:

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

This way, when you call notifyDataSetChanged(), the view pager will remove all views and reload them all. As so the reload effect is obtained.

The second option, suggested by Alvaro Luis Bustamante (previously alvarolb), is to setTag() method in instantiateItem() when instantiating a new view. Then instead of using notifyDataSetChanged(), you can use findViewWithTag() to find the view you want to update.

The second approach is very flexible and high performant. Kudos to alvarolb for the original research.

like image 185
rui.araujo Avatar answered Sep 28 '22 09:09

rui.araujo


I don't think there is any kind of bug in the PagerAdapter. The problem is that understanding how it works is a little complex. Looking at the solutions explained here, there is a misunderstanding and therefore a poor usage of instantiated views from my point of view.

The last few days I have been working with PagerAdapter and ViewPager, and I found the following:

The notifyDataSetChanged() method on the PagerAdapter will only notify the ViewPager that the underlying pages have changed. For example, if you have created/deleted pages dynamically (adding or removing items from your list) the ViewPager should take care of that. In this case I think that the ViewPager determines if a new view should be deleted or instantiated using the getItemPosition() and getCount() methods.

I think that ViewPager, after a notifyDataSetChanged() call takes it's child views and checks their position with the getItemPosition(). If for a child view this method returns POSITION_NONE, the ViewPager understands that the view has been deleted, calling the destroyItem(), and removing this view.

In this way, overriding getItemPosition() to always return POSITION_NONE is completely wrong if you only want to update the content of the pages, because the previously created views will be destroyed and new ones will be created every time you call notifyDatasetChanged(). It may seem to be not so wrong just for a few TextViews, but when you have complex views, like ListViews populated from a database, this can be a real problem and a waste of resources.

So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.

Imagine for example that you have 100 pages with 100 TextViews and you only want to update one value periodically. With the approaches explained before, this means you are removing and instantiating 100 TextViews on each update. It does not make sense...

like image 22
Alvaro Luis Bustamante Avatar answered Sep 28 '22 09:09

Alvaro Luis Bustamante