7

I have a ViewPager set up that is drawing the data for its pages (views) from data passed down from a server. On occasion, the server will send down new data that will re-order the views (and sometimes add new views) and I need to make that happen seamlessly on my android device while the user is currently viewing a particular fragment.

I have it set to call notifyDataSetChanged() when the new data is pulled down from the server, but that seems to keep a cached version of the slides to the left and right of the currently viewed slide which potentially will change with the reorder. I have looked at this topic here: ViewPager PagerAdapter not updating the View and implemented the first solution which works fine, other than it reloads the current slide that is being viewed which won't work for my purposes. I took a shallow-dive look into implementing the setTag() method proposed on that same page in the second answer and that would work for updating information on the slides, but it won't help me for reordering them.

is there some way I can reorder all of the slides behind the scenes w/o causing any burps in the currently viewed slide?

TIA

EDIT: adding code and asking for some further clarification

This is my Adapter class.

    private class ScreenSlidePagerAdapter extends FragmentPagerAdapter {
    ContestEntriesModel entries;

    public ScreenSlidePagerAdapter(FragmentManager fm, ContestEntriesModel Entries) {
        super(fm);
        this.entries = Entries;
    }

    @Override
    public long getItemId(int position) {
        return entries.entries[position].ContestEntryId;

    }

    @Override
    public int getItemPosition(Object object) {
        android.support.v4.app.Fragment f = (android.support.v4.app.Fragment)object;
        for(int i = 0; i < getCount(); i++){

            android.support.v4.app.Fragment fragment = getItem(i);
            if(f.equals(fragment)){
                return i;
            }
        }
        return POSITION_NONE;
    }


    @Override
    public android.support.v4.app.Fragment getItem(int position) {

        long entryId = getItemId(position);
        //Log.d("EntryIds",Integer.toString(entryId));
        if(mItems.get(entryId) != null) {
            return mItems.get(entryId);
        }
        Fragment f = ScreenSlidePageFragment.create(position);
        mItems.put(entryId, f);
        return f;
    }

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

And here is what I'm using when a new payload comes down from the server:

    @Override
    protected void onPostExecute(ContestEntriesModel entries) {
        Fragment currentFragment = ContestEntries.mItems.get(ContestEntries.CurrentEntryId);
        Log.d("fromUpdate",Long.toString(ContestEntries.CurrentEntryId));
        ContestEntries.Entries = entries;
        ContestEntries.NUM_PAGES = ContestEntries.Entries.entries.length;
        ContestEntries.mPagerAdapter.notifyDataSetChanged();
        ContestEntries.mPager.setCurrentItem(ContestEntries.mPagerAdapter.getItemPosition(currentFragment));
    }
Community
  • 1
  • 1
Christopher Johnson
  • 2,571
  • 6
  • 37
  • 62
  • Calling `getItem(int)` in your implementation will create a new Fragment every time, because you didn't implement some form of caching. As you create a new Fragment everytime, `getItemPosition(Object)` will always return `POSITION_NONE`, which is why it reloads the current slide you're on. – Reinier Jun 17 '13 at 21:20
  • ok, can you point me to a resource where I might learn how to implement some form of caching? Also, the fragment object I'm using doesn't have a newInstance() method... – Christopher Johnson Jun 17 '13 at 21:24
  • Explanation: without the caching from my example, the Fragment passed as `Object` will never equal the return value of `getItem(int)`, because the object references are not the same. While you could overcome this by implementing an `equals()`-method in your Fragment, this still would mean you're creating a new Fragment with every call to `getItem(int)` - which is a useless waste of resources. – Reinier Jun 17 '13 at 21:25
  • In my example I used a HashMap for caching/remembering what Fragment belongs to which key/ID. So basically what's missing now is a correct implementation of `getItem(int)`. – Reinier Jun 17 '13 at 21:26
  • ok I see it. I think I understand. I will give it a whirl. Thanks much for your advice. Sorry to make you work so hard for this one :/ – Christopher Johnson Jun 17 '13 at 21:32
  • ok I'm very close. I have it now staying on the same slide when new updates come down from the server. However, it seems to be caching slides to the side of it. How can I force a refresh of everything? Right now I'm just sending down 7 slides in ascending order, then on an update from the server, sending them again in descending order (for testing purposes). As I said, I'm able to maintain the current slide on a new load from the server, but if I'm slide 2 from the first load then do a new load from the server, the ordering isn't reversed as it should be for my testing. – Christopher Johnson Jun 18 '13 at 03:24
  • The ordering actually is reversed, but I have to swipe a few slides down the road to load them in. I need to be able to refresh all of the slides except the one I'm currently viewing but I need to have that one placed in the correct new order. I've updated my OP with the new code I'm using. – Christopher Johnson Jun 18 '13 at 03:28
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31901/discussion-between-christopher-johnson-and-reinier) – Christopher Johnson Jun 18 '13 at 03:34

1 Answers1

6

Check my answer here. The key is to override both getItemId(int) and getItemPosition(Object) in your adapter.

getItemId(int) is used to identify your pages uniquely within your dataset. getItemPosition(Object) is used to fetch the (updated) position for this unique page in your dataset.

If you want to keep focus on the same page before and after updating (adding/removing pages), you should call viewpager.setCurrentItem(int) for the new position of your page after calling .notifyDataSetChanged() on your adapter.

Community
  • 1
  • 1
Reinier
  • 3,776
  • 1
  • 25
  • 28
  • this looks very promising. Admittedly, I'm not an android developer and this is one of my first real projects. I will give your suggestion a try, but be prepared for me to ask some more questions about implementation if you don't mind. – Christopher Johnson Jun 17 '13 at 17:52
  • Ok, I've run into my first challenge. The way I'm doing it is passing down the total number of "slides" from my server. I'm then filling an array with data that is not directly tied to by my pager (no real "dataset" per se) and in a Fragment extension class, I'm just filling my slides based on the index of that array which is passed in through my mPageNumber. I therefore don't think I have a real "key" that I can use in your getItemId() example on your linked page. I can post some code in my OP if you think that would help. – Christopher Johnson Jun 17 '13 at 19:06
  • What kind of items do you receive from your server? Don't they contain some form of unique identifier? Otherwise you could construct an identifier e.g. by hashing the individual items. As `getItemId(int)` is `ViewPager`'s way of identifying unique page-entries, this is the way to go when it comes to having pages with dynamic positions. And yes - sample code would probably help. – Reinier Jun 17 '13 at 20:05
  • ya I realized I could use the unique ID from what is coming from the server. I'm still having some challenges though. I will update my OP with some code and explain the new challenges I"m facing. – Christopher Johnson Jun 17 '13 at 20:48