328

I can't update the content in ViewPager.

What is the correct usage of methods instantiateItem() and getItem() in FragmentPagerAdapter class?

I was using only getItem() to instantiate and return my fragments:

@Override
public Fragment getItem(int position) {
    return new MyFragment(context, paramters);
}

This worked well. Except I can't change the content.

So I found this: ViewPager PagerAdapter not updating the View

"My approach is to use the setTag() method for any instantiated view in the instantiateItem() method"

Now I want to implement instantiateItem() to do that. But I don't know what I have to return (the type is Object) and what is the relation with getItem(int position)?

I read the reference:

  • public abstract Fragment getItem (int position)

    Return the Fragment associated with a specified position.

  • public Object instantiateItem (ViewGroup container, int position)

    Create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup). Parameters

    container The containing View in which the page will be shown. position The page position to be instantiated.

    Returns

    Returns an Object representing the new page. This does not need to be a View, but can be some other container of the page.

but I still don't get it.

Here's my code. I'm using support package v4.

ViewPagerTest

public class ViewPagerTest extends FragmentActivity {
    private ViewPager pager;
    private MyFragmentAdapter adapter; 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pager1);

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

        String[] data = {"page1", "page2", "page3", "page4", "page5", "page6"};

        adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
        pager.setAdapter(adapter);

        ((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                reload();
            }
        });
    }

    private void reload() {
        String[] data = {"changed1", "changed2", "changed3", "changed4", "changed5", "changed6"};
        //adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
        adapter.setData(data);
        adapter.notifyDataSetChanged();
        pager.invalidate();

        //pager.setCurrentItem(0);
    }
}

MyFragmentAdapter

class MyFragmentAdapter extends FragmentPagerAdapter {
    private int slideCount;
    private Context context;
    private String[] data;

    public MyFragmentAdapter(FragmentManager fm, int slideCount, Context context, String[] data) {
        super(fm);
        this.slideCount = slideCount;
        this.context = context;
        this.data = data;
    }

    @Override
    public Fragment getItem(int position) {
        return new MyFragment(data[position], context);
    }

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

    public void setData(String[] data) {
        this.data = data;
    }

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

MyFragment

public final class MyFragment extends Fragment {
    private String text;

    public MyFragment(String text, Context context) {
        this.text = text;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.slide, null);
        ((TextView)view.findViewById(R.id.text)).setText(text);

        return view;
    }
}

Here is also somebody with a similar problem, no answers http://www.mail-archive.com/android-developers@googlegroups.com/msg200477.html

Ixx
  • 29,955
  • 39
  • 123
  • 217
  • To get a better understanding of the components involved it might help to read my tutorial about a tabbed ViewPager with separate back navigation history. It comes with a working example on GitHub and additional resources: https://medium.com/@nilan/separate-back-navigation-for-a-tabbed-view-pager-in-android-459859f607e4 – marktani Jan 03 '16 at 17:45
  • got some issue http://stackoverflow.com/questions/40149039/viewpager-recyclerview-issue – albert Oct 21 '16 at 12:25
  • You can check this [example in GitHub](https://github.com/thedeveloperworldisyours/RefreshCurrentFragmentWithinViewPager). I hope this example helps you. – Cabezas Sep 08 '17 at 09:11
  • Good simple solution: stackoverflow.com/a/35450575/2162226 – Gene Bo May 03 '18 at 19:15
  • Google recently introduced ViewPager2 which simplifies dynamic Fragment/View updates. You can see exemples on https://github.com/googlesamples/android-viewpager2 or this answer on https://stackoverflow.com/a/57393414/1333448 – Yves Delerm Aug 07 '19 at 11:55

21 Answers21

618

When using FragmentPagerAdapter or FragmentStatePagerAdapter, it is best to deal solely with getItem() and not touch instantiateItem() at all. The instantiateItem()-destroyItem()-isViewFromObject() interface on PagerAdapter is a lower-level interface that FragmentPagerAdapter uses to implement the much simpler getItem() interface.

Before getting into this, I should clarify that

if you want to switch out the actual fragments that are being displayed, you need to avoid FragmentPagerAdapter and use FragmentStatePagerAdapter.

An earlier version of this answer made the mistake of using FragmentPagerAdapter for its example - that won't work because FragmentPagerAdapter never destroys a fragment after it's been displayed the first time.

I don't recommend the setTag() and findViewWithTag() workaround provided in the post you linked. As you've discovered, using setTag() and findViewWithTag() doesn't work with fragments, so it's not a good match.

The right solution is to override getItemPosition(). When notifyDataSetChanged() is called, ViewPager calls getItemPosition() on all the items in its adapter to see whether they need to be moved to a different position or removed.

By default, getItemPosition() returns POSITION_UNCHANGED, which means, "This object is fine where it is, don't destroy or remove it." Returning POSITION_NONE fixes the problem by instead saying, "This object is no longer an item I'm displaying, remove it." So it has the effect of removing and recreating every single item in your adapter.

This is a completely legitimate fix! This fix makes notifyDataSetChanged behave like a regular Adapter without view recycling. If you implement this fix and performance is satisfactory, you're off to the races. Job done.

If you need better performance, you can use a fancier getItemPosition() implementation. Here's an example for a pager creating fragments off of a list of strings:

ViewPager pager = /* get my ViewPager */;
// assume this actually has stuff in it
final ArrayList<String> titles = new ArrayList<String>();

FragmentManager fm = getSupportFragmentManager();
pager.setAdapter(new FragmentStatePagerAdapter(fm) {
    public int getCount() {
        return titles.size();
    }

    public Fragment getItem(int position) {
        MyFragment fragment = new MyFragment();
        fragment.setTitle(titles.get(position));
        return fragment;
    }

    public int getItemPosition(Object item) {
        MyFragment fragment = (MyFragment)item;
        String title = fragment.getTitle();
        int position = titles.indexOf(title);

        if (position >= 0) {
            return position;
        } else {
            return POSITION_NONE;
        }
    }
});

With this implementation, only fragments displaying new titles will get displayed. Any fragments displaying titles that are still in the list will instead be moved around to their new position in the list, and fragments with titles that are no longer in the list at all will be destroyed.

What if the fragment has not been recreated, but needs to be updated anyway? Updates to a living fragment are best handled by the fragment itself. That's the advantage of having a fragment, after all - it is its own controller. A fragment can add a listener or an observer to another object in onCreate(), and then remove it in onDestroy(), thus managing the updates itself. You don't have to put all the update code inside getItem() like you do in an adapter for a ListView or other AdapterView types.

One last thing - just because FragmentPagerAdapter doesn't destroy a fragment doesn't mean that getItemPosition is completely useless in a FragmentPagerAdapter. You can still use this callback to reorder your fragments in the ViewPager. It will never remove them completely from the FragmentManager, though.

H. Saul
  • 322
  • 4
  • 13
Bill Phillips
  • 7,597
  • 1
  • 22
  • 13
  • 4
    Doesn't work, it's still not updating anything... posted my code. – Ixx Jun 02 '12 at 18:53
  • 68
    Okay, figured out the problem - you need to use FragmentStatePagerAdapter rather than FragmentPagerAdapter. FragmentPagerAdapter never removes fragments from the FragmentManager, it only detaches and attaches them. Check the docs for more info - in general, you only want to use FragmentPagerAdapter for fragments that are permanent. I've edited my example with the correction. – Bill Phillips Jun 04 '12 at 17:38
  • 1
    I will review this at some point later... thanks for your answer. And then I accept your answer if it works. Before your last comment I implemented to remove and add a new ViewPager each time and that works. Not very good solution, but I don't have time now to change it. – Ixx Jun 12 '12 at 12:08
  • 12
    You're right, changing the class of the adapter to FragmentStatePagerAdapter fixed all my trouble. Thanks! – Ixx Jun 17 '12 at 22:06
  • i've tried your nice answer but it doesn't solve my issue , my problem is here http://stackoverflow.com/questions/16117981/refreshing-views-of-fragments-inside-of-a-viewpager – Arash GM Apr 20 '13 at 10:47
  • -Bill Phillips i am new to android , can you provide a same code about MyFragment class. – hemant Apr 22 '13 at 07:16
  • Hemant - MyFragment is just a placeholder class name. It would be whatever fragment class you are using in your own application. – Bill Phillips Apr 23 '13 at 00:50
  • Hey, this works for the most part. However, I'm having trouble getting any fragment's view using a page swipe. I have multiple ways to progress in this page swiping a 'next' button that removes that item from the adapter and notifies that adapter and updates accordingly, or swiping the page adapter (using it's native implementation). When I swipe, I can get to the next one, but not the one after that. It seems to not be adding the next items to the fragment manager yet. Any thoughts? – jbenowitz May 01 '13 at 22:06
  • Implementing a FragmentStatePagerAdapter and overriding the getItemPosition fixed my problem of having the viewpager in my fragment which was in a tabhost not updating after removing a fragment. Cost me a day to screw around with, and your answer fixed it in minutes. Thank you! – MacD May 13 '13 at 15:15
  • God, thank you, this has been driving me nuts. I had tried the `FragmentStatePageAdapter` and the `getItemPosition`, but not the two together. Trying them together worked, thank you! – Stavros Korokithakis Jun 20 '13 at 11:30
  • Hi, I've been reading all the answers in this thread however - I'm working on a viewpager that handles a bunch of list views instead of fragments. I want to update one listview every 30/60 seconds based on the position it is in, in the view pager. Can someone point me in the right direction? – shecodesthings Aug 12 '13 at 18:23
  • 1
    I think you'll get better feedback if you submit a standalone question for that, darkravedev. – Bill Phillips Aug 14 '13 at 15:34
  • I just change the `FragmentPagerAdapter` to `FragmentStatePageAdapter `, it's working :'( you saved my life !!! – Kannika Aug 01 '14 at 14:27
  • What is `MyFragment` and where to know about it, thanks! – Alston Aug 08 '14 at 06:07
  • That answer solved my issue, but I didn't get why he's getting indexOf(title) in order to return the position when we can simply use PositionUnchanged instead. Or did I misunderstand this? – leoneboaventura Nov 18 '14 at 10:33
  • 1
    PositionUnchanged would solve the problem, too, yeah. The given code will also handle the situation where items are moving around, if you need that. – Bill Phillips Nov 18 '14 at 14:37
  • @BillPhillips .. Hi can u check out my post http://stackoverflow.com/questions/30231180/how-to-update-the-list-whenever-the-tabs-are-changing-in-view-pager .. I tried your answer but none are working .. can you please help me out – anand May 21 '15 at 10:31
  • @BillPhillips thanks a lot!!! one week of headache gone in a second!! i just used FragmentStatePagerAdapter instead of FragmentPagerAdapter! now my pages are updating and everytime view pager adapter is loading the page again!! Thank you so much! :) :) – AndroidManifester Jun 23 '15 at 11:18
  • @BillPhillips can you look at my question? It's not working. I am following your answer. http://stackoverflow.com/questions/32129247/viewpager-not-getting-updated-using-fragmentstatepageradapter – moDev Aug 22 '15 at 14:34
  • 2
    Even when I return POSITION_NONE, while using `FragmentStatePagerAdapter`, I can see that it removes my fragment and creates the new instance. But the new instance receives a savedInstanceState Bundle with the state of an old fragment. How can I completely destroy the fragment so Bundle is null when created again? – Singed Sep 08 '16 at 12:24
  • @BillPhillips can you help in my issue http://stackoverflow.com/questions/40149039/viewpager-recyclerview-issue – albert Oct 22 '16 at 05:36
  • i have used this adapter but my issue is i get in correct indexes when i remove items – Mr.G Dec 27 '16 at 13:06
  • Ok so can someone explain to why we have all struggled so much with this and what is Google's explanation for this? The better I get at coding, the more I think some of these developers at Android are either certifiably brilliant or the exact opposite...I am leaning toward the the last option. This is so ridiculous, that is all it took to fix things and make the Adapter work the way we all seem to want it to work so why is Google so far out of touch with us App makers...baffling... – JamisonMan111 Apr 06 '18 at 06:39
  • Can confirm what @Singed said - the new instance of the fragment receives an old saved state. Not working as expected. – Kirill Starostin Sep 09 '19 at 06:39
  • 1
    Solution - create a new adapter each time you need to completely swap the items. – Kirill Starostin Sep 09 '19 at 08:11
147

Instead of returning POSITION_NONE from getItemPosition() and causing full view recreation, do this:

//call this method to update fragments in ViewPager dynamically
public void update(UpdateData xyzData) {
    this.updateData = xyzData;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(Object object) {
    if (object instanceof UpdateableFragment) {
        ((UpdateableFragment) object).update(updateData);
    }
    //don't return POSITION_NONE, avoid fragment recreation. 
    return super.getItemPosition(object);
}

Your fragments should implement UpdateableFragment interface:

public class SomeFragment extends Fragment implements
    UpdateableFragment{

    @Override
    public void update(UpdateData xyzData) {
         // this method will be called for every fragment in viewpager
         // so check if update is for this fragment
         if(forMe(xyzData)) {
           // do whatever you want to update your UI
         }
    }
}

and the interface:

public interface UpdateableFragment {
   public void update(UpdateData xyzData);
}

Your data class:

public class UpdateData {
    //whatever you want here
}
M-WaJeEh
  • 16,562
  • 9
  • 59
  • 93
  • 1
    Does this mean that your MainActivity also needs to implement the interface ??? Please help me out here. In my current implementation, I am using my MainActivity class as a communicator between the fragments, so I was confused about this solution. – Rakeeb Rajbhandari Nov 20 '13 at 09:44
  • 1
    Ok I have answered at another place with full implementation where I am updating `Fragment` dynamically, have a look: http://stackoverflow.com/questions/19891828/scroll-all-the-listview-in-a-viewpager/20089116#20089116 – M-WaJeEh Nov 20 '13 at 12:50
  • @M-WaJeEh maybe some example please!!! i have a listview with list of cities and after selecting city opens viewpager (3 pages) with information about this city. it works ok. but after selecting another city viewpager shows only the 3`d page on refreshes the 1-st page only after swiping pages? thw 2`d page is always blank. thanks – SERG Apr 22 '14 at 12:45
  • @SERG Here is a full working example http://stackoverflow.com/questions/19891828/synchronize-all-the-listview-in-a-viewpager/20089116#20089116 – M-WaJeEh Apr 22 '14 at 17:05
  • 4
    among all the stuff i ve seen for the past 2.5 days of my work life.. this is the only one that worked for me.. all sorts of arigato, thanks, spaciba, sukran .. n. – Orkun Ozen Mar 05 '15 at 17:05
  • 2
    Mother of Dragons!, this works like a charm, the only solution worked for me... thx a lot !! – Sebastián Guerrero Apr 17 '15 at 20:09
  • It make some consequences in another fragment in the same view pager! Did anyone notice that? I am using fragmentstatepageradapter. When i do the above the another fragment data is null. – Rethinavel Aug 25 '15 at 08:21
  • @RethinavelPillai You are supposed to put some information in `UpdateData` which then can be used to discard update inside fragment. – M-WaJeEh Jun 07 '16 at 09:22
  • Any idea on mine? http://stackoverflow.com/questions/40095826/how-to-update-a-fragment-that-has-a-gridview-populated-from-sqlite – Si8 Oct 20 '16 at 10:12
  • It worked for me until Support library 25.1.0 came out. Now it no longer works. .( Anyone has an idea? – Guillem Roca Feb 21 '17 at 14:01
  • Hi, can you please suggest same approach for PagerAdapter ? I dont have fragments, just activity and viewpager with PagerAdapter. – Hardik Joshi May 03 '17 at 05:20
  • @M-WaJeEh can you tell me what is UpdateData? – humayoon siddique May 16 '17 at 06:53
  • @M-WaJeEh we can not declare main static class we just write static classes inner main class means child classes can be static in Android – humayoon siddique May 16 '17 at 08:00
  • I was assuming that you would put it inside your `BaseFragment` or whatever. Anyways updated my answer to make it just a class. – M-WaJeEh May 16 '17 at 08:10
  • @M-WaJeEh I have four fragments that load through one actvity using tablayout and viewpager and i confused add this class in where – humayoon siddique May 16 '17 at 09:34
  • @M-WaJeEh Your way is very nice! The update is slow in my case. Have you got that problem? The old data appears before being replaced. – Elisabeth Aug 12 '17 at 18:21
  • @M-WaJeEh Hey what if i want to delete an view from view pager in same way as you mentioned ? Can you please help me out ? It shows blank page after removing an view and then not able to swipe. – Ritesh Adulkar Dec 24 '18 at 07:44
26

for those who still face the same problem which i faced before when i have a ViewPager with 7 fragments. the default for these fragments to load the English content from API service but the problem here that i want to change the language from settings activity and after finish settings activity i want ViewPager in main activity to refresh the fragments to match the language selection from the user and load the Arabic content if user chooses Arabic here what i did to work from the first time

1- You must use FragmentStatePagerAdapter as mentioned above.

2- on mainActivity i override the onResume and did the following

if (!(mPagerAdapter == null)) {
    mPagerAdapter.notifyDataSetChanged();
}

3-i overrided the getItemPosition() in mPagerAdapter and make it return POSITION_NONE.

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

works like charm

Rahul Gaur
  • 1,413
  • 9
  • 23
Ziad Gholmish
  • 783
  • 1
  • 7
  • 10
  • 1
    Works except in one situtaion. When you delete last item in viewPager. Then it doesn't remove from list. – Vlado Pandžić Jul 11 '16 at 13:57
  • I was adding fragments to both the left and right side of the current fragment in the viewpager dynamatically. This one worked! Thanks. – h8pathak Dec 19 '18 at 19:02
15

I have encountered this problem and finally solved it today, so I write down what I have learned and I hope it is helpful for someone who is new to Android's ViewPager and update as I do. I'm using FragmentStatePagerAdapter in API level 17 and currently have just 2 fragments. I think there must be something not correct, please correct me, thanks. enter image description here

  1. Serialized data has to be loaded into memory. This can be done using a CursorLoader/AsyncTask/Thread. Whether it's automatically loaded depends on your code. If you are using a CursorLoader, it's auto-loaded since there is a registered data observer.

  2. After you call viewpager.setAdapter(pageradapter), the adapter's getCount() is constantly called to build fragments. So if data is being loaded, getCount() can return 0, thus you don't need to create dummy fragments for no data shown.

  3. After the data is loaded, the adapter will not build fragments automatically since getCount() is still 0, so we can set the actually loaded data number to be returned by getCount(), then call the adapter's notifyDataSetChanged(). ViewPager begin to create fragments (just the first 2 fragments) by data in memory. It's done before notifyDataSetChanged() is returned. Then the ViewPager has the right fragments you need.

  4. If the data in the database and memory are both updated (write through), or just data in memory is updated (write back), or only data in the database is updated. In the last two cases if data is not automatically loaded from the database to memory (as mentioned above). The ViewPager and pager adapter just deal with data in memory.

  5. So when data in memory is updated, we just need to call the adapter's notifyDataSetChanged(). Since the fragment is already created, the adapter's onItemPosition() will be called before notifyDataSetChanged() returns. Nothing needs to be done in getItemPosition(). Then the data is updated.

JDJ
  • 4,198
  • 3
  • 22
  • 43
oscarthecat
  • 1,652
  • 1
  • 16
  • 27
  • i have found the nothing need to do after you update data(in memeory), just call `notifyDataSetChanged()` then just updated automatically, because the fragment(with chartview) i use will refresh itself. if it's not, like a static fragment, i will have to call `onItemPositon()` and returns `POSITION_NONE` sorry about that – oscarthecat Sep 01 '13 at 07:54
  • Does anyone have an idea of what tool was used to make that diagram? – JuiCe Aug 11 '16 at 15:17
13

Try destroyDrawingCache() on ViewPager after notifyDataSetChanged() in your code.

dakshbhatt21
  • 3,318
  • 3
  • 27
  • 37
czaku
  • 810
  • 9
  • 17
  • 3
    Same here. Works great thank you. I had especially a tough time cause I am not using `Fragments` in my view pager. So thank you! – Seth Apr 08 '15 at 20:12
11

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
  • can you help me in my issue http://stackoverflow.com/questions/40149039/viewpager-recyclerview-issue – albert Oct 22 '16 at 05:36
  • can you please help me with this issue http://stackoverflow.com/questions/41547995/java-lang-illegalstateexception-the-application-pageradapter-changed-the-adapte/41548210?noredirect=1#comment70327157_41548210 – viper Jan 17 '17 at 04:31
10

For some reason none of the answers worked for me so I had to override the restoreState method without calling super in my fragmentStatePagerAdapter. Code:

private class MyAdapter extends FragmentStatePagerAdapter {

    // [Rest of implementation]

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

}
hordurh
  • 2,653
  • 1
  • 14
  • 17
  • 1
    After trying everything else in this SO answer, this is the one that worked for me. I'm finish()ing an activity that's returning to the Activity that hosts the PagerAdapter in question. I want all my fragments to be re-created in this case because the Activity that I just finished may have changed the state that is used to draw the fragments. – JohnnyLambada Nov 23 '16 at 01:28
  • OMG.. it works.. it works.. kudos :D In my case i need to remove viewpager current item object. but the current fragment is not updating after trying all the above methods/steps. – Rahul Khurana May 05 '17 at 07:53
3

I slightly modified the solution provided by Bill Phillips to suit my needs

private class PagerAdapter extends FragmentStatePagerAdapter{
    Bundle oBundle;
    FragmentManager oFragmentManager;
    ArrayList<Fragment> oPooledFragments;

public PagerAdapter(FragmentManager fm) {
    super(fm);
    oFragmentManager=fm;

    }
@Override
public int getItemPosition(Object object) {

    Fragment oFragment=(Fragment)object;
    oPooledFragments=new ArrayList<>(oFragmentManager.getFragments());
    if(oPooledFragments.contains(oFragment))
        return POSITION_NONE;
    else
        return POSITION_UNCHANGED;
    } 
}

so that the getItemPosition() returns POSITION_NONE only for those fragments which are currently in the FragmentManager when getItemPosition is called. (Note that this FragmentStatePager and the ViewPager associated with it are contained in a Fragment not in a Activity)

Piyush
  • 23,959
  • 6
  • 36
  • 71
adwait
  • 31
  • 1
  • 6
2

I had a similar problem but don't want to trust on the existing solutions (hard coded tag names etc.) and I couldn't make M-WaJeEh's solution work for me. Here is my solution:

I keep references to the fragments created in getItem in an array. This works fine as long as the activity is not destroyed due to configurationChange or lack of memory or whatever (--> when coming back to the activity, fragments return to their last state without 'getItem' being called again and thus without updating the array).

To avoid this problem I implemented instantiateItem(ViewGroup, int) and update my array there, like this:

        @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object o = super.instantiateItem(container, position);
        if(o instanceof FragmentX){
            myFragments[0] = (FragmentX)o;
        }else if(o instanceof FragmentY){
            myFragments[1] = (FragmentY)o;
        }else if(o instanceof FragmentZ){
            myFragments[2] = (FragmentZ)o;
        }
        return o;
    }

So, on the one hand I'm happy that I found a solution that works for me and wanted to share it with you, but I also wanted to ask whether somebody else tried something similar and whether there is any reason why I shouldn't do it like that? So far it works very good for me...

jpm
  • 2,686
  • 1
  • 15
  • 20
  • I've done something related in http://stackoverflow.com/a/23427215/954643, although you should probably remove the item from `myFragments` in `public void destroyItem(ViewGroup container, int position, Object object)` as well so you don't grab a stale fragement reference. – qix May 08 '14 at 18:28
2

I have lived same problem and I have searched too much times. Any answer given in stackoverflow or via google was not solution for my problem. My problem was easy. I have a list, I show this list with viewpager. When I add a new element to head of the list and I refresh the viewpager nothings changed. My final solution was very easy anybody can use. When a new element added to list and want to refresh the list. Firstly set viewpager adapter to null then recreate the adapter and set i to it to viewpager.

myVPager.setAdapter(null);
myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(),newList);
myVPager.setAdapter(myFragmentAdapter);

Be sure your adapter must extend FragmentStatePagerAdapter

nebyan
  • 9,107
  • 1
  • 18
  • 19
1

I use EventBus library to update Fragment content in ViewPager. The logic is simple, just like document of EventBus how to do. It is no need to control FragmentPagerAdapter instance. The code is here:

1: Define events

Define which message which is needed to update.

public class UpdateCountEvent {
    public final int count;

    public UpdateCountEvent(int count) {
        this.count = count;
    }
}

2.Prepare subscribers

Write below code in the Fragment which is needed update.

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);  
    super.onStop();
}

public void onEvent(UpdateCountEvent event) {//get update message
    Toast.makeText(getActivity(), event.count, Toast.LENGTH_SHORT).show();
}

3.Post events

Write below code in other Activity or other Fragment which needs to update parameter

//input update message
EventBus.getDefault().post(new UpdateCountEvent(count));
Fantasy Fang
  • 5,256
  • 2
  • 23
  • 25
1

I had been trying so many different approaches, none really sove my problem. Below are how I solve it with a mix of solutions provided by you all. Thanks everyone.

class PagerAdapter extends FragmentPagerAdapter {

    public boolean flag_refresh=false;

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

    @Override
    public Fragment getItem(int page) {
        FragmentsMain f;
        f=new FragmentsMain();
        f.page=page;
        return f;
    }

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

    @Override
    public int getItemPosition(Object item) {
        int page= ((FragmentsMain)item).page;

        if (page == 0 && flag_refresh) {
            flag_refresh=false;
            return POSITION_NONE;
        } else {
            return super.getItemPosition(item);
        }
    }

    @Override
    public void destroyItem(View container, int position, Object object) {

        ((ViewPager) container).removeView((View) object);
    }
}

I only want to refresh page 0 after onResume().

 adapter=new PagerAdapter(getSupportFragmentManager());
 pager.setAdapter(adapter);

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

    if (adapter!=null) {
        adapter.flag_refresh=true;
        adapter.notifyDataSetChanged();
    }
}

In my FragmentsMain, there is public integer "page", which can tell me whether it is the page I want to refresh.

public class FragmentsMain extends Fragment {

private Cursor cursor;
private static Context context;
public int page=-1;
Ted Hsieh
  • 19
  • 3
1

If you want to use FragmentStatePagerAdapter, please take a look at https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=37990. There are issues with FragmentStatePagerAdapter that may or may not trouble your use case.

Also, link has few solutions too..few may suit to your requirement.

cgr
  • 4,423
  • 2
  • 24
  • 48
  • From that URL I found solution. I used the modified FragmentStatePagerAdapter from this gist https://gist.github.com/ypresto/8c13cb88a0973d071a64 in my code and it started to work as supposed to be. – Rafael Feb 22 '16 at 04:02
  • Cool. Yes. Link has few solutions too. – cgr Feb 22 '16 at 07:53
1

I know am late for the Party. I've fixed the problem by calling TabLayout#setupWithViewPager(myViewPager); just after FragmentPagerAdapter#notifyDataSetChanged();

theapache64
  • 7,353
  • 7
  • 45
  • 80
1

This might be of help to someone - in my case when inserting a new page the view pager was asking for the position of an existing fragment twice, but not asking for the position of the new item, causing incorrect behaviour and data not displaying.

  1. Copy the source for for FragmentStatePagerAdapter (seems to have not been updated in ages).

  2. Override notifyDataSetChanged()

    @Override
    public void notifyDataSetChanged() {
        mFragments.clear();
        super.notifyDataSetChanged();
    }
    
  3. Add a sanity check to destroyItem() to prevent crashes:

    if (position < mFragments.size()) {
        mFragments.set(position, null);
    }
    
Meanman
  • 1,363
  • 18
  • 16
1

Here is my implementation that incorporates the info from @Bill Phillips One gets fragment caching most of the time, except when the data has changed. Simple, and seems to work fine.

MyFragmentStatePagerAdapter.java

private boolean mIsUpdating = false;
public void setIsUpdating(boolean mIsUpdating) {
        this.mIsUpdating = mIsUpdating;
    }


@Override
public int getItemPosition(@NonNull Object object) {

    if (mIsUpdating) {
        return POSITION_NONE;
    }
    else {
        return super.getItemPosition(object);
    }
}

MyActivity.java

mAdapter.setIsUpdating(true);
mAdapter.notifyDataSetChanged();
mAdapter.setIsUpdating(false);
voam
  • 818
  • 11
  • 23
1

Using ViewPager2 and FragmentStateAdapter:

Updating data dynamically is supported by ViewPager2.

There is an important note in the docs on how to get this working:

Note: The DiffUtil utility class relies on identifying items by ID. If you are using ViewPager2 to page through a mutable collection, you must also override getItemId() and containsItem(). (emphasis mine)

Based on ViewPager2 documentation and Android's Github sample project there are a few steps we need to take:

  1. Set up FragmentStateAdapter and override the following methods: getItemCount, createFragment, getItemId, and containsItem (note: FragmentStatePagerAdapter is not supported by ViewPager2)

  2. Attach adapter to ViewPager2

  3. Dispatch list updates to ViewPager2 with DiffUtil (don't need to use DiffUtil, as seen in sample project)


Example:

private val items: List<Int>
    get() = viewModel.items
private val viewPager: ViewPager2 = binding.viewPager

private val adapter = object : FragmentStateAdapter(this@Fragment) {
    override fun getItemCount() = items.size
    override fun createFragment(position: Int): Fragment = MyFragment()
    override fun getItemId(position: Int): Long = items[position].id
    override fun containsItem(itemId: Long): Boolean = items.any { it.id == itemId }
}
viewPager.adapter = adapter
    
private fun onDataChanged() {
    DiffUtil
        .calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize(): Int = viewPager.adapter.itemCount
            override fun getNewListSize(): Int = viewModel.items.size
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                viewPager.adapter?.getItemId(oldItemPosition) == viewModel.items[newItemPosition].id

            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                areItemsTheSame(oldItemPosition, newItemPosition)
        }, false)
        .dispatchUpdatesTo(viewPager.adapter!!)
}
jacoballenwood
  • 2,050
  • 1
  • 19
  • 33
0

This solution won't work for everyone, but in my case, every Fragment in my ViewPager is a different class, and only one of them ever exist at a time.

With this constraint, this solution is safe and should be safe to use in production.

private void updateFragment(Item item) {

    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    for (Fragment fragment : fragments) {

        if (fragment instanceof MyItemFragment && fragment.isVisible()) {
            ((MyItemFragment) fragment).update(item);
        }

    }
}

If you have multiple versions of the same fragment, you can use this same strategy to call methods on those fragments to determine if it is the fragment you wish to update.

Kevin Grant
  • 2,131
  • 21
  • 16
0

I've gone through all the answers above and a number of others posts but still couldn't find something that worked for me (with different fragment types along with dynamically adding and removing tabs). FWIW following approach is what worked for me (in case anyone else has same issues).

public class MyFragmentStatePageAdapter extends FragmentStatePagerAdapter {
    private static final String TAB1_TITLE = "Tab 1";
    private static final String TAB2_TITLE = "Tab 2";
    private static final String TAB3_TITLE = "Tab 3";

    private ArrayList<String> titles = new ArrayList<>();
    private Map<Fragment, Integer> fragmentPositions = new HashMap<>();


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


    public void update(boolean showTab1, boolean showTab2, boolean showTab3) {
        titles.clear();

        if (showTab1) {
            titles.add(TAB1_TITLE);
        }
        if (showTab2) {
            titles.add(TAB2_TITLE);
        }
        if (showTab3) {
            titles.add(TAB3_TITLE);
        }
        notifyDataSetChanged();
    }


    @Override
    public int getCount() {
        return titles.size();
    }

    @Override
    public Fragment getItem(int position) {
        Fragment fragment = null;

        String tabName = titles.get(position);
        if (tabName.equals(TAB1_TITLE)) { 
            fragment =  Tab1Fragment.newInstance();
        } else if (tabName.equals(TAB2_TITLE)) {
            fragment =  Tab2Fragment.newInstance();
        } else if (tabName.equals(TAB3_TITLE)) {
            fragment =  Tab3Fragmen.newInstance();
        } 

        ((BaseFragment)fragment).setTitle(tabName);
        fragmentPositions.put(fragment, position);
        return fragment;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titles.get(position);
    }

    @Override
    public int getItemPosition(Object item) {
        BaseFragment fragment = (BaseFragment)item;
        String title = fragment.getTitle();
        int position = titles.indexOf(title);

        Integer fragmentPosition = fragmentPositions.get(item);
        if (fragmentPosition != null && position == fragmentPosition) {
            return POSITION_UNCHANGED;
        } else {
            return POSITION_NONE;
        }
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        fragmentPositions.remove(object);
    }
}
John O'Reilly
  • 8,536
  • 4
  • 31
  • 49
0

Use FragmentStatePagerAdapter instead of FragmentPagerAdapter if you want to recreate or reload fragment on index basis For example if you want to reload fragment other than FirstFragment, you can check instance and return position like this

 public int getItemPosition(Object item) {
    if(item instanceof FirstFragment){
       return 0;
    }
    return POSITION_NONE;
 }
HemangNirmal
  • 601
  • 8
  • 23
0

You need change instantiateItem's mFragments element getItemPosition.

    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);

        if (f != null) {
            int newPosition = getItemPosition(f);
            if (newPosition == POSITION_UNCHANGED) {
                return f;
            } else if (newPosition == POSITION_NONE) {
                mFragments.set(position, null);
            } else {
                mFragments.set(newPosition, f);
            }
        }
    }

Based AndroidX FragmentStatePagerAdapter.java, because mFragments's elements position do not change when calling notifyDataSetChanged().

Source: https://github.com/cuichanghao/infivt/blob/master/library/src/main/java/cc/cuichanghao/library/FragmentStatePagerChangeableAdapter.java

Example: https://github.com/cuichanghao/infivt/blob/master/app/src/main/java/cc/cuichanghao/infivt/MainActivityChangeablePager.kt

You can run this project to confirm how to work. https://github.com/cuichanghao/infivt

changhao cui
  • 298
  • 3
  • 7