623

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) {
        }
    }
}
A-Sharabiani
  • 13,270
  • 12
  • 87
  • 109
C0deAttack
  • 23,507
  • 18
  • 68
  • 79
  • 1
    Please see http://stackoverflow.com/questions/12510404/reorder-pages-in-fragmentstatepageradapter-using-getitempositionobject-object for a potential bug with the FragmentStatePagerAdapter. – samis Jan 21 '14 at 20:12
  • 2
    Sorry to dredge this up. Your question really helped me so thanks! The one thing I don't get is how the reference to data in the PagerAdapter gets changed when you update it in the Activity – Ewanw Jul 13 '15 at 22:54
  • Make your adapter extend BaseAdapter Refer this: http://stackoverflow.com/questions/13664155/dynamically-add-and-remove-view-to-viewpager –  Aug 12 '15 at 13:04
  • 1
    Apparently if you set the adapter again to your view pager, then it resets. – EpicPandaForce Feb 11 '16 at 21:27
  • i have issue in pager http://stackoverflow.com/questions/41217237/viewpager-not-getting-last-item – chris Dec 21 '16 at 12:27
  • Good simple solution: stackoverflow.com/a/35450575/2162226 – Gene Bo May 03 '18 at 19:14
  • @gnB thatis not a solution to this problem. and copypaste neither – Emanuel Graf May 05 '18 at 06:59
  • After several years, Google ended by giving a solution to this problem. It is clean, performant, and avoids unmaintained custom code. Please see my answer https://stackoverflow.com/a/57393414/1333448 – Yves Delerm Aug 07 '19 at 11:44

41 Answers41

883

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.

Community
  • 1
  • 1
rui.araujo
  • 9,327
  • 1
  • 15
  • 24
  • 4
    Better than my work around, seems to not have any side affects, thanks. – C0deAttack Sep 03 '11 at 21:39
  • 3
    I have a similar problem. My viewPager will show the new searc results but doesn't default back to the first entry in the cursor. Does anyone know how to reset the position when updating the dataset? – mAndroid Sep 14 '11 at 05:16
  • 1
    @mAndroid you should file a separate question with more details. – rui.araujo Sep 15 '11 at 08:53
  • This worked for me and suited my purposes. I'll try the poster below's comment and see where the difference is. – Cody Jan 04 '12 at 17:43
  • this also worked with actionbar sherlock's actionbar + tab/pager, this solution would update every single tab :) – tom91136 Feb 04 '12 at 05:26
  • I kindly recommed to take a look at the solution i propose below... This one could be a waste of resources if you have pages with many information, database queries, and so on... – Alvaro Luis Bustamante Apr 18 '12 at 12:44
  • 1
    returning POSITION_NONE apparently adds a lot of inefficiency to the overall application as this causes Android to delete the view associated with the queried position. The view then needs to be re-created again before displaying. Solves the problem of views not being updated, but probably not the best solution for complex layouts. – wufoo Jun 19 '12 at 20:36
  • @rui.araujo can you pls help me out in updating the content of Fragment view in `ViewPager`. this is question http://stackoverflow.com/questions/13472566/how-to-update-the-fragment-ui-in-viewpager-dynamically – Nishant Nov 21 '12 at 06:20
  • 1
    I know this is an old post, but why not use the Observer pattern? I was also searching for a solution and I ended up coming up with the following, which I think works pretty well and is very simple to implement -- https://gist.github.com/alexfu/5797429. – Alex Fu Jun 17 '13 at 14:49
  • 12
    Better answer (without removing all items) -> http://stackoverflow.com/a/10852046/898056 – yhpark Jul 10 '13 at 18:52
  • 1
    The above method really slows down the initial loading of content into viewpager. Could be used only for very simple layouts – Yasir Jun 26 '14 at 00:03
  • Never just use POSITION_NONE use if(fragmentManager.getFragments().contains(object)) return POSITION_NONE; else return POSITION_UNCHANGED; to avoid Fatal Exception: java.lang.IllegalStateException Fragment {} is not currently in the FragmentManager – Tyler Davis Sep 23 '14 at 23:04
  • @rui.araujo notifyDataSetChange() will update all views, but I want to update a particular tab in the pageradapter. Is it possible? please advise – harishannam Jan 10 '15 at 15:57
  • for some reason, when idly viewing the view pager, it refreshes the pages for no apparent reason every so often when using `POSITION_NONE` which has an adverse effect on my application since my pages call webservices on `onAcctivityCreated` maybe its coincidence, but this behaviour only started happening when i did this so yeah... – Fonix Jan 20 '15 at 07:30
  • when i refresh the viewpagers data, first two view pagers are not refresh and other view pager data is changing. please help me – kartheeki j Oct 08 '15 at 08:09
  • @rui.araujo need help http://stackoverflow.com/questions/41217237/viewpager-not-getting-last-item – chris Dec 21 '16 at 12:28
  • I needed all cards to update so this POSITION_NONE solution is perfect for me. Thanks! – hcpl Mar 08 '17 at 08:46
  • 1
    The top, accepted answer is just a link to the second most popular answer? lol wat – edthethird May 08 '17 at 15:01
  • just to make it efficient u can do thi => @Override public int getItemPosition(@NonNull Object object) { if(refresh){ refresh = false; return POSITION_NONE; }else{ return super.getItemPosition(object); } } u can set the value of refresh from outside when u want the adapter to refresh – Harkal Aug 05 '19 at 02:25
  • this solution is not working with viewpager2. Any suggestions? – user1090751 Sep 05 '20 at 08:25
488

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...

JDJ
  • 4,198
  • 3
  • 22
  • 43
Alvaro Luis Bustamante
  • 7,487
  • 3
  • 26
  • 37
  • 11
    +1 - Your solution not only works like a charm I ran in exactly the problem you described here when I had to update pages which contained a tabs with listviews, textviews and images. (OutOfErrorExceptions and such ...) Thank you! – schlingel Dec 15 '11 at 16:23
  • Yes, this solution is much more elegant! Now it would be nice if there was a Google I/O video explaining this, the same way ListView was explained... – albnok Dec 28 '11 at 10:32
  • 1
    At the time, I did not look up the side effects. It worked and it solved my itch. Still, there should be a way of actually refreshing all the pages automatically, and my approach does that. Though it is only good if you to really changes the view itself. Your approach is a nicer and I will remember when I only need to update a single view. – rui.araujo Feb 08 '12 at 19:20
  • 1
    Hi alvarolb. Using method indeed seems to be the way to go. I tried in my current implementation of viewpager (https://gist.github.com/2067626); I see my data loaded on the first screen, but the subsequent screens are blank. I know the code I have provided is very limited - is this the way you are using tags? Also, I noticed that destroyItem(...) gets called every 3 swipes on the viewpager. Why is that? Any help would be greatly appreciated. – Raunak Mar 18 '12 at 01:44
  • Hi Raunak, the use of tags is useful only for retrieve active views associated with this Tag. Its use in the instantiateItem is not necessary, since this method is only called once for each position (unless destroyItem where called), so instantiateItem should only inflate the view and set the corresponding tag for this position. But, what tag? I have an arrayList whit all the objects i want to show, in your case may be strings. So each tag is the object in the given position of the array. The arrayList is used also to return the size in the getCount(). – Alvaro Luis Bustamante Mar 18 '12 at 16:22
  • 1
    The destroyItem call is normal, since the instantiateItem/destroyItem is called on demand depending on the current "scroll" position. It makes no sense to have i.e. 500 views loaded in memory if you only can display one. AFAIK, 3 views are loaded in memory: LeftView|CenterView|RightView so yo can scroll smoothly between them. If you swipe to right, the LeftView is destroyed and a new RightView is Loaded... Hope this help! – Alvaro Luis Bustamante Mar 18 '12 at 16:27
  • Problem we're solving is similar to an ebook reader where a person can swipe left/right to navigate to the next page or to the previous page. Prior to loading 3 views - left, current, right - into memory at a time, viewpager automatically works out if a view should be deleted (if outside of current left-center-right positions) or instantiated (if within current left-center-right positions) using the getItemPosition() and getCount() methods. In our requirement, is there a need for your setTag() method as it seems the viewpager is performing the required functions automatically? – Henry Thornton Mar 21 '12 at 10:37
  • dbv, in your application i think that you don't need the setTag. The idea of setTag is used here when you need to update some contents in a loaded view without having to destroy and instantiate the view each time.. in your case, book pages doesn't change over the time – Alvaro Luis Bustamante Apr 13 '12 at 22:25
  • Alvarolb - Great approach. I've added some code based on your suggestion above in another response. Thanks for the suggestion! – Grimmace Jun 06 '12 at 02:00
  • I override the getItemPosition and return POSITION_NONE just to check but destroyItem was never called.. why is that? although my adapter now refereshes correctly and frankly speaking @alvarolb can you please explain a little more.. didn't understand much.. – Farhan Oct 24 '12 at 10:30
  • This is indeed right solution, thank you, I've made just a small addition thanks to your detailed explanation. In methode getItemPosition I am querying received view getTag methode and if id is in list for refresh I am returning POSITION_NONE, just for flagged positions. It works flawlessly. – Gordon Freeman Jun 14 '13 at 08:25
  • Sorry, I couldn't implement this ... I am a little new ... could you please see this http://stackoverflow.com/questions/20091171/implementation-of-the-settag-and-findviewwithtag-in-instantiateitem-of-the – Rakeeb Rajbhandari Nov 20 '13 at 08:53
  • thanks for your smart mind. but if i want to add one new view to 0 position, there is it a efficient way to shift all view one hop? and how can implement this? – 013 Dec 02 '13 at 06:46
  • 3
    @alvarolb : Hello, I don't really know how you mean about `setTag` in method `onInstantiateItem`, Would you like to updated this answer with some coding? Thanks. – Huy Tower Apr 11 '14 at 09:50
  • @AlexTran when you implement instantiateItem, you will probably inflate a view or similar. For a given view instance, you can call setTag method with a given value (the view identification, its index, etc). So, later, when you want to update the view, you can call fidViewWithTag in viewPager with the parameter you established onInstantiateItem. Hope this help. – Alvaro Luis Bustamante Apr 11 '14 at 10:07
  • @alvarolb So If I have 6 different pages (6 `Fragments`) and have many Views (`public static` `TextView, LinearLayout` ...) need to `setTag`, I will declare all them? It is what you mean, right? – Huy Tower Apr 11 '14 at 10:15
  • 1
    @AlexTran If you are using fragments i will not recommend using PagerAdapter, as a Fragment does not provide setTag method. Use in this case a FragmentPagerAdapter or FragmentPagerStateAdapter. – Alvaro Luis Bustamante Apr 11 '14 at 10:24
  • `FragmentPagerAdapter or FragmentPagerStateAdapter` I know about the different among them. `FragmentPagerAdapter` not automatically reload the page while swiping, `FragmentPagerStateAdapter` can. But I used `FragmentPagerStateAdapter`, my issue happened since the first time I reach `ViewPager`, The method `public Fragment getItem(int position)` will be called automatically twice, you know that? I don't want that. I want it was only called one time. – Huy Tower Apr 11 '14 at 10:30
  • 1
    The loading page will load `Page 1` -> `Page 2` in the same time, So I only get information at the second page rather the first page, u see? – Huy Tower Apr 11 '14 at 10:55
  • @AlexTran ViewPager loads a minimum of offscreen pages to allow fast swipe between them. Why is this a problem? Page1 and Page2 are loaded, so when you swipe to right, the Page2 is already rendered an visible. Then Page3 (if any) will be loaded at this time, and so on... – Alvaro Luis Bustamante Apr 11 '14 at 11:00
  • 1
    @alvarolb : I see your idea. Assume I have this case : 1.Already loaded first page and second page. Currently I'm in first page. 2. I pressed the `Button` to update current page (Current `Views`). But nothing updated in here (It means it not automatically refresh). /// You understand? I need swipe to third page, and swipe back to see the first page already updated. So how I can refresh the `Views` directly without swipe to and swipe back. – Huy Tower Apr 11 '14 at 11:06
  • @AlexTran if you are using fragments, i recommend you to implement a FragmentPagerAdapter or FragmentStatePagerAdapter, and create a method like this one i have uploaded: http://pastebin.com/t5jxjUDq The returning fragment can be casted to your fragment type and call some update method you can implement in your fragment, that tell your views to update. Hope this help. – Alvaro Luis Bustamante Apr 11 '14 at 11:24
  • 1
    @AlvaroLuisBustamante Hi, can you provide some sample code? I a new beginner and really want some code to study. Please help... – Alston Aug 12 '14 at 07:47
  • Absolutely well said. I shudder to think of all the pager adapters out there always returning POSITION_NONE. – Greg Ennis Sep 05 '14 at 21:10
  • @AlvaroLuisBustamante, Alex: Like Alex mention it load page 1, 2 in same time, so what if some changes that doing on page first wanted to apply on page second when swipe from page 1 to 2, since there will be no "instantiateItem" method call this time for page 2...please suggest any solution! – CoDe Nov 06 '14 at 10:32
  • 40
    It would be perfect if you wrote an example. – Sami Eltamawy May 19 '15 at 11:31
  • my pager not working..i have fragment http://stackoverflow.com/questions/31476778/pager-adapter-not-displaying-image-after-getting-from-response – Aditya Jul 17 '15 at 13:29
  • @HuyTower I had the same problem. Will Alvaro's method update the views BEFORE and AFTER the view currently on screen? – the_prole Dec 03 '15 at 06:47
  • @AlvaroLuisBustamante will your method update the two views directly before and after the view currently on screen, which have already been loaded into memory to allow smooth swiping? – the_prole Dec 03 '15 at 06:50
  • @the_prole : Unfortunately, I don't follow this answer guy. I have the other way, should be better. You can use [`OnPageChangeListener`](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwj46OPwrb_JAhUJnpQKHW-YCOAQFggdMAA&url=http%3A%2F%2Fdeveloper.android.com%2Freference%2Fandroid%2Fsupport%2Fv4%2Fview%2FViewPager.OnPageChangeListener.html&usg=AFQjCNH26yNqE26Cs0z58z4oFYxjXPIhdg&sig2=G_ajtU9qPxhNRLNKEYPPlA) you will never get this issue anymore. – Huy Tower Dec 03 '15 at 09:18
  • 15
    As someone says : an example woulb be appreciated! – Jey10 Jan 21 '16 at 14:25
  • 20
    6 years now , no one can provide an example. – Oussaki Aug 21 '17 at 18:18
  • 2
    8 years now , no one can provide an example. – AndroidRuntimeException Aug 20 '19 at 12:21
  • Hi, Also if you follow same method using findViewById instead of findViewWithTag its works. tell if if this is same or are there any drawbacks. thanks for describing and explaining in such a nice way. – Fazal Jan 15 '20 at 06:34
  • Example: `val view = viewPager.findViewWithTag(myTag)` Use: `viewPagerAdapter.setupUI(view!!, )` Please note that `setupUI` is a custom function I call from `instantiateItem()` with `itemView` and pojo to setup data. – Paras Sidhu Sep 04 '20 at 10:20
  • 1
    9 years now, no one can provide an example – deaddroid Oct 05 '20 at 20:15
  • An example will never be provided. Please, move to `ViewPager2`. – CoolMind Feb 08 '21 at 08:02
  • And this is best example for it.. https://www.tothenew.com/blog/updating-viewpager-with-new-data-dynamically/ – Mrunal May 07 '21 at 07:53
88

Change the FragmentPagerAdapter to FragmentStatePagerAdapter.

Override getItemPosition() method and return POSITION_NONE.

Eventually, it will listen to the notifyDataSetChanged() on view pager.

Jorgesys
  • 114,263
  • 22
  • 306
  • 247
Kit
  • 2,120
  • 11
  • 11
47

The answer given by alvarolb is definitely the best way to do it. Building upon his answer, an easy way to implement this is to simply store out the active views by position:

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

Then once by overriding the notifyDataSetChanged method you can refresh the views...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

You can actually use similar code in instantiateItem and notifyDataSetChanged to refresh your view. In my code I use the exact same method.

Community
  • 1
  • 1
Grimmace
  • 3,933
  • 1
  • 28
  • 25
  • 8
    can you provide a full adapter example? is this being done using Fragments or not? –  Jan 30 '13 at 16:22
  • 9
    ?? what is the mean of it either we have to add he view here or nothing ? – Akarsh M Jul 22 '13 at 15:39
  • This method update all the views. If you only want to update one view, then you can write your own notifyDataItemChanged method like this: public void notifyDataItemChanged(int pos) { TextView tv = (TextView) views.get(pos).findViewById(R.id.tv); tv.setText(list.get(pos)); super.notifyDataSetChanged(); } – Kai Wang Mar 08 '16 at 20:57
  • This code crashes if new pager size is less than old size – Anton Duzenko Mar 24 '16 at 15:41
  • @AntonDuzenko correct, deleting views is quite hard. – MLProgrammer-CiM Jul 05 '16 at 18:34
  • If you want to use an iterable interface to this, you can replace SparseArray with a HashMap – Andy M Sep 25 '18 at 07:08
29

Had the same problem. For me it worked to extend FragmentStatePagerAdapter, and override the below methods:

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}
tonys
  • 3,692
  • 29
  • 37
Mario Mosca
  • 416
  • 4
  • 6
  • This is the only solution worked for me too. My case is not updating the fragment, but removing or adding fragments dynamically. Without extending FragmentStatePagerAdapter, ViewPager returns fragments cached, which is an incorrect position. Thanks! – Sira Lam Nov 07 '17 at 04:28
  • Works well on support library 28.0.0 – Oleksii K. Feb 14 '19 at 21:00
  • Override to do what? How to create a state object associated with an Adapter? – Phani Rithvij Dec 21 '19 at 14:56
20

After hours of frustration while trying all the above solutions to overcome this problem and also trying many solutions on other similar questions like this, this and this which all FAILED with me to solve this problem and to make the ViewPager to destroy the old Fragment and fill the pager with the new Fragments. I have solved the problem as following:

1) Make the ViewPager class to extends FragmentPagerAdapter as following:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Create an Item for the ViewPager that store the title and the fragment as following:

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) Make the constructor of the ViewPager take my FragmentManager instance to store it in my class as following:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) Create a method to re-set the adapter data with the new data by deleting all the previous fragment from the fragmentManager itself directly to make the adapter to set the new fragment from the new list again as following:

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) From the container Activity or Fragment do not re-initialize the adapter with the new data. Set the new data through the method setPagerItems with the new data as following:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

I hope it helps.

Community
  • 1
  • 1
Sami Eltamawy
  • 9,350
  • 8
  • 46
  • 66
9

I had the same issue and my solution is using FragmentPagerAdapter with overriding of FragmentPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

By default, this method returns item's position. I suppose that ViewPager checks if itemId was changed and recreates page only if it was. But not-overriden version returns the same position as itemId even if page is actually different, and ViewPager doesn't define that page is replaced one and needs to be recreated.

To use this, long id is needed for each page. Normally it is expected to be unique, but i suggest, for this case, that it just should be different from the previous value for the same page. So, It is possible to use continuous counter in adapter or random integers (with wide distribution) here.

I think that it is more consistent way rather using of Tags of view mentioned as a solution in this topic. But probably not for all cases.

atlascoder
  • 1,667
  • 1
  • 16
  • 31
8

I found very interesting decision of this problem. Instead of using FragmentPagerAdapter, which keep in memory all fragments, we can use FragmentStatePagerAdapter (android.support.v4.app.FragmentStatePagerAdapter), that reload fragment each time, when we select it.

Realisations of both adapters are identical. So, we need just change "extend FragmentPagerAdapter" on "extend FragmentStatePagerAdapter"

5

A much easier way: use a FragmentPagerAdapter, and wrap your paged views onto fragments. They do get updated

Jorgesys
  • 114,263
  • 22
  • 306
  • 247
alfongj
  • 1,883
  • 1
  • 17
  • 22
5

All these solution did not help me. thus i found a working solution: You can setAdapter every time, but it isn't enough. you should do these before changing adapter:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

and after this:

viewPager.setAdapter(adapter);
Ziem
  • 6,059
  • 7
  • 49
  • 83
Mahdi Perfect
  • 314
  • 3
  • 6
  • Thanks! In my case I used `ViewPager` inside fragment, so replaced `slideShowPagerAdapter.getFragmentManager()` with `getChildFragmentManager()`. Maybe `getFragmentManager()` will help in your case. I used `FragmentPagerAdapter`, not `FragmentStatePagerAdapter`. Also see https://stackoverflow.com/a/25994654/2914140 for a hacky way. – CoolMind Dec 11 '18 at 14:52
5

Two and half years after the OP posed his question, this issue is still, well, still an issue. It's obvious Google's priority on this isn't particularly high, so rather than find a fix, I found a workaround. The big breakthrough for me was finding out what the real cause of the problem was (see the accepted answer in this post ). Once it was apparent that the issue was that any active pages are not properly refreshed, my workaround was obvious:

In my Fragment (the pages):

  • I took all the code which populates the form out of onCreateView and put it in a function called PopulateForm which may be called from anywhere, rather than by the framework. This function attempts to get the current View using getView, and if that is null, it just returns. It's important that PopulateForm contains only the code that displays - all the other code which creates FocusChange listeners and the like is still in OnCreate
  • Create a boolean which can be used as a flag indicating the form must be reloaded. Mine is mbReloadForm
  • Override OnResume() to call PopulateForm() if mbReloadForm is set.

In my Activity, where I do the loading of the pages:

  • Go to page 0 before changing anything. I'm using FragmentStatePagerAdapter, so I know that two or three pages are affected at most. Changing to page 0 ensures I only ever have the problem on pages 0, 1 and 2.
  • Before clearing the old list, take it's size(). This way you know how many pages are affected by the bug. If > 3, reduce it to 3 - if you're using a a different PagerAdapter, you'll have to see how many pages you have to deal with (maybe all?)
  • Reload the data and call pageAdapter.notifyDataSetChanged()
  • Now, for each of the affected pages, see if the page is active by using pager.getChildAt(i) - this tells you if you have a view. If so, call pager.PopulateView(). If not, set the ReloadForm flag.

After this, when you reload a second set of pages, the bug will still cause some to display the old data. However, they will now be refreshed and you will see the new data - your users won't know the page was ever incorrect because this refreshing will happen before they see the page.

Hope this helps someone!

Community
  • 1
  • 1
RichardP
  • 512
  • 5
  • 7
5

Thank rui.araujo and Alvaro Luis Bustamante. At first, I try to use rui.araujo's way, because it's easy. It works but when the data change, the page will redraw obviously. It is bad so I try to use Alvaro Luis Bustamante's way. It's perfect. Here is the code:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return view.equals(object);
    }

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

And when data change:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}
evan.z
  • 81
  • 1
  • 2
5

ViewPager was not designed to support dynamic view change.

I had confirmation of this while looking for another bug related to this one https://issuetracker.google.com/issues/36956111 and in particular https://issuetracker.google.com/issues/36956111#comment56

This question is a bit old, but Google recently solved this problem with ViewPager2 . It will allow to replace handmade (unmaintained and potentially buggy) solutions by a standard one. It also prevents recreating views needlessly as some answers do.

For ViewPager2 examples, you can check https://github.com/googlesamples/android-viewpager2

If you want to use ViewPager2, you will need to add the following dependency in your build.gradle file :

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Then you can replace your ViewPager in your xml file with :

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

After that, you will need to replace ViewPager by ViewPager2 in your activity

ViewPager2 needs either a RecyclerView.Adapter, or a FragmentStateAdapter, in your case it can be a RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

In the case you were using a TabLayout, you can use a TabLayoutMediator :

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

Then you will be able to refresh your views by modifying your adapter's data and calling notifyDataSetChanged method

Yves Delerm
  • 680
  • 4
  • 9
5

After a lot of searching for this problem, I found a really good solution that I think is the right way to go about this. Essentially, instantiateItem only gets called when the view is instantiated and never again unless the view is destroyed (this is what happens when you override the getItemPosition function to return POSITION_NONE). Instead, what you want to do is save the created views and either update them in the adapter, generate a get function so someone else can update it, or a set function which updates the adapter (my favorite).

So, in your MyViewPagerAdapter add a variable like:

private View updatableView;

an in your instantiateItem:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

so, this way, you can create a function that will update your view:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

Hope this helps!

German
  • 89
  • 1
  • 2
  • I realized that I wasn't setting the data in `instantiateItem` that's why my views weren't updating. From your answer I realized it. +1 – Phani Rithvij Dec 21 '19 at 15:07
4

Just in case anyone are using FragmentStatePagerAdapter based adapter(which will let ViewPager create minimum pages needed for display purpose, at most 2 for my case), @rui.araujo's answer of overwriting getItemPosition in your adapter will not cause significant waste, but it still can be improved.

In pseudo code:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}
Jerry Tian
  • 3,426
  • 4
  • 24
  • 27
  • inside ViewPager, it was quite easy to know item past position, the most perfect implementation is just return the new position in `getItemPosition()` when dataset changed, and ViewPager will know they are changed or not. sadly, ViewPager didn't do it. – VinceStyling Jul 25 '14 at 07:19
3

I am just posting this answer in case anyone else finds it useful. For doing the exact same thing, I simply took the source code of the ViewPager and PagerAdapter from the compatibility library and compiled it within my code (You need to sort out all the errors and imports yourself, but it definitely can be done).

Then, in the CustomViewPager, create a method called updateViewAt(int position). The view itself can be gotten from ArrayList mItems defined in the ViewPager class (you need to set an Id for the views at instantiate item and compare this id with position in the updateViewAt() method). Then you can update the view as necessary.

Dallas
  • 1,022
  • 1
  • 8
  • 24
Naveen LP
  • 51
  • 2
3

I had a similar problem in which I had four pages and one of the pages updated views on the other three. I was able to updated the widgets(SeekBars, TextViews, etc.) on the page adjacent to the current page. The last two pages would have uninitialized widgets when calling mTabsAdapter.getItem(position).

To solve my issue, I used setSelectedPage(index) before calling getItem(position). This would instantiate the page, allowing me to be able to alter values and widgets on each page.

After all of the updating I would use setSelectedPage(position) followed by notifyDataSetChanged().

You can see a slight flicker in the ListView on the main updating page, but nothing noticeable. I haven't tested it throughly, but it does solve my immediate problem.

Devin Burke
  • 13,074
  • 11
  • 51
  • 80
enKoder
  • 41
  • 2
  • Hi, i am interested in your code, i am facing similar issue, I am able to update adjacent fragments but the current fragment which is visible doesn't get updated. I am very confused with all answers here. how can i update views in current fragment which is shown by the VIewPager adapter – Pankaj Nimgade Jun 11 '15 at 14:23
2

I guess, I've got the logics of ViewPager.

If I need to refresh a set of pages and display them based on new dataset, I call notifyDataSetChanged(). Then, ViewPager makes a number of calls to getItemPosition(), passing there Fragment as an Object. This Fragment can be either from an old dataset (that I want to discard) or from a new one (that I want to display). So, I override getItemPosition() and there I have to determine somehow if my Fragment is from the old dataset or from the new one.

In my case I have a 2-pane layout with a list of top items on the left pane and a swipe view (ViewPager) on the right. So, I store a link to my current top item inside my PagerAdapter and also inside of each instantiated page Fragment. When the selected top item in the list changes, I store the new top item in PagerAdapter and call notifyDataSetChanged(). And in the overridden getItemPosition() I compare the top item from my adapter to the top item from my fragment. And only if they are not equal, I return POSITION_NONE. Then, PagerAdapter reinstantiates all the fragments that have returned POSITION_NONE.

NOTE. Storing the top item id instead of a reference might be a better idea.

The code snippet below is a bit schematical but I adapted it from the actually working code.

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

Thanks for all the previous researchers!

Stanislav Karakhanov
  • 1,149
  • 1
  • 9
  • 11
2

The code below worked for me.

Create a class which extends the FragmentPagerAdapter class as below.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

@Override
public int getCount() {
    return tabCount;
}}

Then inside each Fragment you created, create an updateFragment method. In this method you change the things you need to change in the fragment. For example in my case, Fragment0 contained a GLSurfaceView which displays a 3d object based on a path to a .ply file, so inside my updateFragment method I change the path to this ply file.

then create a ViewPager instance,

viewPager = (ViewPager) findViewById(R.id.pager);

and an Adpater instance,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

then do this,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Then inside the class were you initialized the Adapter class above and created a viewPager, every time you want to update one of your fragments (in our case Fragment0) use the following:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

This solution was based on the technique suggested by Alvaro Luis Bustamante.

Brainnovo
  • 1,574
  • 2
  • 8
  • 15
2

I know I'm late but still it could help someone. I'm just extending the accepted answer and I have also added the comment on it.

Well, the answer itself says it is inefficient

So in order to make it refresh only when required you can do this

private boolean refresh;

public void refreshAdapter() {
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if (refresh) {
        refresh = false;
        return POSITION_NONE;
    } else {
        return super.getItemPosition(object);
    }
}
Manaus
  • 387
  • 4
  • 9
Harkal
  • 1,589
  • 8
  • 28
1

1.First you have to set the getItemposition method in your Pageradapter class 2.You have to read the Exact position of your View Pager 3.then send that position as data location of your new one 4.Write update button onclick listener inside the setonPageChange listener

that program code is little bit i modified to set the particular position element only

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    data = new ArrayList<String>();
    data.add("A");
    data.add("B");
    data.add("C");
    data.add("D");
    data.add("E");
    data.add("F");

    myViewPager = (ViewPager) findViewById(R.id.pager);
    myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            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 int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @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) {
    }
}
}
Naveen Kumar
  • 1,394
  • 1
  • 10
  • 12
1

what worked for me was going viewPager.getAdapter().notifyDataSetChanged();

and in the adapter putting your code for updating the view inside getItemPosition like so

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

might not be the most correct way of going about it but it worked (the return POSITION_NONE trick caused a crash for me so wasnt an option)

Ziem
  • 6,059
  • 7
  • 49
  • 83
Fonix
  • 10,588
  • 2
  • 41
  • 67
1

You can update dynamically all fragments, you can see in three steps.

In your adapter:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

Now in your activity:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Finally in your fragment, something like that:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

You can see complete code here.

Thanks Alvaro Luis Bustamante.

Cabezas
  • 7,330
  • 6
  • 59
  • 63
1

Always returning POSITION_NONE is simple but a little inefficient way because that evoke instantiation of all page that have already instantiated.

I've created a library ArrayPagerAdapter to change items in PagerAdapters dynamically.

Internally, this library's adapters return POSITION_NONE on getItemPosiition() only when necessary.

You can change items dynamically like following by using this library.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

Thils library also support pages created by Fragments.

T.Nakama
  • 29
  • 1
  • 1
  • 2
1

This is for all those like me, which need to update the Viewpager from a service (or other background thread) and none of the proposals have worked: After a bit of logchecking i realized, that the notifyDataSetChanged() method never returns. getItemPosition(Object object) is called an all ends there without further processing. Then i found in the docs of the parent PagerAdapter class (is not in the docs of the subclasses), "Data set changes must occur on the main thread and must end with a call to notifyDataSetChanged() ". So, the working solution in this case was (using FragmentStatePagerAdapter and getItemPosition(Object object) set to return POSITION_NONE) :

and then the call to notifyDataSetChanged() :

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });
Helmwag
  • 420
  • 4
  • 6
1

You can add pager transform on Viewpager like this

myPager.setPageTransformer(true, new MapPagerTransform());

In the below code I changed my view color on runtime when pager scroll

public class MapPagerTransform implements ViewPager.PageTransformer {

    public void transformPage(View view, float position) {
        LinearLayout showSelectionLL = (LinearLayout) view.findViewById(R.id.showSelectionLL);

        if (position < 0) {
            showSelectionLL.setBackgroundColor(Color.WHITE);
        } else if (position > 0) {
            showSelectionLL.setBackgroundColor(Color.WHITE);
        } else {
            showSelectionLL.setBackgroundColor(Color.RED);
        }
    }
}
Manaus
  • 387
  • 4
  • 9
0

I actually use notifyDataSetChanged() on ViewPager and CirclePageIndicator and after that I call destroyDrawingCache() on ViewPager and it works.. None of the other solutions worked for me.

Gustavo Barbosa
  • 1,320
  • 1
  • 17
  • 27
czaku
  • 810
  • 9
  • 17
  • The original question is nearly 2 years old, I'm sure there are many differences in the Android API by now that the answers here may not be right for all versions of the Android API. – C0deAttack Jul 09 '13 at 09:28
0

Instead of returning POSITION_NONE and creating all fragments again, you can do as I suggested here: Update ViewPager dynamically?

Community
  • 1
  • 1
M-WaJeEh
  • 16,562
  • 9
  • 59
  • 93
0

I think I've made a simple way to notify of data set changes:

First, change a bit the way the instantiateItem function works:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

for "updateView" , fill the view with all the data you wish to fill (setText,setBitmapImage,...) .

verify that destroyView works like this:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Now, suppose you need to change the data, do it, and then call the next function on the PagerAdapter :

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

For example if you wish to notify all of the views that are being shown by the viewPager that something has changed, you can call:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

That's it.

android developer
  • 106,412
  • 122
  • 641
  • 1,128
0

For what it's worth, on KitKat+ it seems that adapter.notifyDataSetChanged() is enough to cause the new views to show up, provided that you've setOffscreenPageLimit sufficiently high. I'm able to get desired behavior by doing viewPager.setOffscreenPageLimit(2).

mszaro
  • 1,282
  • 1
  • 12
  • 25
0

This is a horrible problem and I'm happy to present an excellent solution; simple, efficient, and effective !

See below, the code shows using a flag to indicate when to return POSITION_NONE

public class ViewPagerAdapter extends PagerAdapter
{
    // Members
    private boolean mForceReinstantiateItem = false;

    // This is used to overcome terrible bug that Google isn't fixing
    // We know that getItemPosition() is called right after notifyDataSetChanged()
    // Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
    @Override
    public int getItemPosition(Object object)
    {
        if (mForceReinstantiateItem)
        {
            mForceReinstantiateItem = false;
            return POSITION_NONE;
        }
        else
        {
            return super.getItemPosition(object);
        }
    }

    public void setData(ArrayList<DisplayContent> newContent)
    {
        mDisplayContent = newContent;
        mForceReinstantiateItem = true;
        notifyDataSetChanged();
    }

}
Someone Somewhere
  • 22,369
  • 11
  • 111
  • 155
  • My proposed solution appears to work, however, after using for a while I noticed that the 'cached' offscreen views become stale if you update the data in the array. It seems therefore that `ViewPager.notifyDataSetChanged()` is totally FUBAR and the best solution (only when dealing with ViewPager) is to recreate the adapter when the data needs to change. Other views that require an adapter work just fine with `notifyDataSetChanged()` – Someone Somewhere Aug 07 '16 at 11:53
  • This was never a bug, you only misunderstood the `ViewPager`'s behavior. – M D P Nov 24 '18 at 03:03
0

I leave here my own solution, which is a workaround because seems as the problem is the FragmentPagerAdapter doesn't clean the previous fragments, you can be added to the ViewPager, in the Fragment Manager. So, I create a method to execute before add the FragmentPagerAdapter:

(In my case I never add more than 3 fragments, but you can use for example getFragmentManager().getBackStackEntryCount() and check all the fragments.

/**
 * this method is solving a bug in FragmentPagerAdapter which don't delete in the fragment manager any previous fragments in a ViewPager.
 *
 * @param containerId
 */
public void cleanBackStack(long containerId) {
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    for (int i = 0; i < 3; ++i) {
        String tag = "android:switcher:" + containerId + ":" + i;
        Fragment f = getFragmentManager().findFragmentByTag(tag);
        if (f != null) {
            transaction.remove(f);
        }
    }
    transaction.commit();
}

I know it is a workaround because it will stop work if the way the framework is creating the tags change.

(currently "android:switcher:" + containerId + ":" + i)

Then the way to use it is after getting the container:

ViewPager viewPager = (ViewPager) view.findViewById(R.id.view_pager);
cleanBackStack(viewPager.getId());
rafsanahmad007
  • 23,062
  • 6
  • 43
  • 58
lm2a
  • 659
  • 1
  • 9
  • 17
0

In my case every time the Fragment entered onCreateView I was restarting values so handle null or empty values like:

if (var == null) {
    var = new Something();
}

if (list.isEmpty()) {
    list = new ArrayList<MyItem>();
}

if (myAdapter == null) {
    myAdapter = new CustomAdapter();
    recyclerView.setAdapter(myAdapter);
}

// etc...

Fragment class could enter onCreateView every time we change and get back to the view from the UI at runtime.

Manaus
  • 387
  • 4
  • 9
Ninja Coding
  • 1,087
  • 1
  • 14
  • 23
0

I m using Tablayout with ViewPagerAdapter. For passing data between fragments or for communicating between fragments use below code which works perfectly fine and refresh the fragment when ever it appears. Inside button click of second fragment write below code.

b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String text=e1.getText().toString(); // get the text from EditText

            // move from one fragment to another fragment on button click
            TabLayout tablayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); // here tab_layout is the id of TabLayout which is there in parent Activity/Fragment
            if (tablayout.getTabAt(1).isSelected()) { // here 1 is the index number of second fragment i-e current Fragment

                LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
                Intent i = new Intent("EDIT_TAG_REFRESH");
                i.putExtra("MyTextValue",text);
                lbm.sendBroadcast(i);

            }
            tablayout.getTabAt(0).select(); // here 0 is the index number of first fragment i-e to which fragment it has to moeve

        }
    });

below is the code which has to be written in first fragment (in my case) i-e in receiving Fragment.

MyReceiver r;
Context context;
String newValue;
public void refresh() {
    //your code in refresh.
    Log.i("Refresh", "YES");
}
public void onPause() {
    super.onPause();

    LocalBroadcastManager.getInstance(context).unregisterReceiver(r);
}
public void onResume() {
    super.onResume();
    r = new MyReceiver();
    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(r,
            new IntentFilter("EDIT_TAG_REFRESH"));
} // this code has to be written before onCreateview()


 // below code can be written any where in the fragment
 private class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    PostRequestFragment.this.refresh();
        String action = intent.getAction();
        newValue=intent.getStringExtra("MyTextValue");
        t1.setText(newValue); // upon Referesh set the text
    }
}
Tara
  • 2,362
  • 19
  • 29
0

Something that worked for me was to override the finishUpdate method, which is called at the end of a transition and do the notifyDataSetChanged() there:

public class MyPagerAdapter extends FragmentPagerAdapter {
    ...
    @Override
    public void finishUpdate(ViewGroup container) {
        super.finishUpdate(container);
        this.notifyDataSetChanged();
    }
    ...
}
horro
  • 1,068
  • 2
  • 13
  • 34
0

The correct way to override getItemPosition():

@Override
public int getItemPosition(@NonNull Object object) {
    if (!(object instanceof MyPageView)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    MyDataObject dataObject = ((MyPageView) object).getData();
    if (!(dataObject instanceof MyDataObject)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    if (lstItems.contains(dataObject)) {
        return POSITION_UNCHANGED;
    }
    return POSITION_NONE;
}

In short, you need to attach/save the data you are using to paint your view, to your view. From there, when getItemPosition() is invoked, the ViewPager can determine if the currently displayed view is still present on the adapter or not, and act accordingly.

Reaper
  • 354
  • 2
  • 11
0

You just need this simple code:

Code:

private void updateAdapter() {
    if (adapterViewPager != null) {
        int from = vpMyViewPager.getCurrentItem() - vpMyViewPager.getOffscreenPageLimit();
        int to = vpMyViewPager.getCurrentItem() + vpMyViewPager.getOffscreenPageLimit();
        vpMyViewPager.removeAllViews();
        for (int i = from; i <= to; i++) {
            if (i < 0) {
                continue;
            }
            adapterViewPager.instantiateItem(vpMyViewPager, i);
        }
    }
}

Explanation:

If you haven't changed offscreenPageLimit of the ViewPager, it always has 3 to 4 children depending on which direction you are going. And in order to show the correct content inside its children, it uses the adapter to get the right content. now when you call removeAllViews() on your ViewPager, only 3 to 4 Views are actually being removed from the Window's hierarchy, and by calling instantiateItem(ViewGroup viewPager, int index), you are only recreating 3 to 4 Views. Then everything is back to normal, you swipe and scroll, the ViewPager shows contents using its adapter. The number of its children is not the same on all devices in all situations, for example if you set the offscreenPageLimit to 5, it will probably have around 11 to 12 children, but that's all, it's not much. it's fast.

Manaus
  • 387
  • 4
  • 9
M D P
  • 4,209
  • 2
  • 18
  • 34
0

The best solution from my experience : https://stackoverflow.com/a/44177688/3118950, which is override the long getItemId() and return unique ID instead of the default position. In addition to that answer is imported to notice that old fragment will be kept in the fragment manager in case the total amount is less than the page limit and onDetach()/onDestory() will not be called when the fragment is replaced.

0

All these answers are not working for me.

The only one worked for me is that I have to set the adapter to viewpager again, then it will refresh the content.

removeView(int pos) in my PagerAdaper

public void removeView(int index) {
       imageFileNames.remove(index);
       notifyDataSetChanged();
}

wherever I am removing the file I have to do like this

imagePagerAdapter.removeView(currentPosition);
viewPager.setAdapter(imagePagerAdapter);

EDIT:

This below method is effective, you can apply the below one.

public void updateView(int pos){
      viewPager.setAdapter(null);
      imagePagerAdapter =new ImagePagerAdapter(YOUR_CONTEXT,YOUR_CONTENT);
      viewPager.setAdapter(imagePagerAdapter);
      viewPager.setCurrentItem(pos);
}

replace YOUR_CONTEXT with your context and your content with your content name i.e. updated list or something.

sai Pavan Kumar
  • 827
  • 5
  • 18
0

In my case there is a textView in my Viewpager, on a button click in mainActivity I want to change the color of that textView and update pagerAdapter. On the button Click I saved the color in SharedPreference and update pagerAdapter, that it can update the color taken from shared prefrence. So, I update viewPager view the following way .

btn_purple.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int color = ContextCompat.getColor(mContext, R.color.colorPrimaryDark2);
                editor.putInt("sahittoFontColor", color);
                editor.apply();
                toNotifyDatasetChanged();
            }
        });

now the update method :

 private  void  toNotifyDatasetChanged (){
        if(viewPager!=null&& pagerAdapter!=null) {
            viewPager.setAdapter(null);
            viewPager.setAdapter(pagerAdapter);
           
        }

    }

And my pagerAdapter Was :

pagerAdapter = new  Sahitto_ViewPagerAdapter (mContext, filenameParameter, 30, lineList);
                        viewPager.setAdapter(pagerAdapter);

And in instantiateItem was (in PagerAdapter) :

    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
    int   bnfntcolor=settings.getInt("sahittoFontColor", 0);
if (bnfntcolor!=0){
    textView.setTextColor(bnfntcolor);
}

Thus, when I click the button, the color changes immediately in pagerAdapter's Textview.

Happy coding.

Noor Hossain
  • 603
  • 8
  • 18
-12

Trigger mTabsAdapter.onTabChanged(mTabHost.getCurrentTabTag()); before updating the view. This will work.

sth
  • 200,334
  • 49
  • 262
  • 354