0

I have tried using an extended FragmentStatePagerAdapter and an extended FragmentPagerAdapter to achieve what I'm trying and neither seem to work. I am sending down a JSON object from my server to fill an object model on the android client. On first load, everything works perfectly, I can swipe through pages just fine from beginning to end. Here is my object model:

ContestEntriesModel.java

public class ContestEntriesModel {
    public int ContestId;
    public String ContestName;
    public ContestEntryModel[] entries;
}

As you can see, there is an array of ContestEntryModel. Each of those models looks like this:

ContestEntryModel.java

public class ContestEntryModel {
    public long ContestEntryId;
    public int UserId;
    public String FullName;
    public int Comments;
    public string ImageUrl;
}

As stated above, when the first data payload comes down from the server, I am filling one fragment per item in the ContestEntryModel[] array with data from that model (including an image from an internet resource (ImageUrl)), and that works perfectly. I can swipe until my fingers bleed and all is good in the world.

I'm trying to now send another payload down from the server which has the items in the array in a different order (I will ultimately add new items too, but one step at a time). I have searched high and low on StackOverflow for a solution to this and posted a question yesterday which got me close to where I need to be (thanks Reinier), but I'm still struggling with getting all of my fragments to reorder and have the adapter completely refresh with the new order. The adapter seems to cache a few of the slides (the ones adjacent to the one I'm currently viewing) and they don't "refresh" until I've slid a few images past them and then return. Yesterday's trouble was getting the currently viewed slide to not "refresh" when the new data comes down from the server which I have working...sort of. Let's say I have 7 items in my array and I swipe to slide #3 (index 2) on the first load. I then push new data from the server which is just ordering the array in reverse. The currently viewed slide (slide #3) should then move to index position 4 and I should only be able to swipe to the right 2 times (index position 5 and 6). Instead what is happening is that it's remembering the current index position for that slide (index 2) and keeping it there. As I said above, slide #2 (index 1) and slide #4 (index 3) are also being maintained. My expected behavior is that those slides should reverse positions around slide #3 (index 2).

Here is my implementation of my PagerAdapter (this is the version that is a FragmentStatePagerAdapter not a FragmentPagerAdapter:

ScreenSlidePagerAdapter:

public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ContestEntriesModel entries;

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

    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)){
                //Log.d("currentPosition",Integer.toString(i));
                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;
    }
}

currently I'm setting a static property on my activity for the adapter (I use it in a few other "external" classes) like so:

MainActivity:

    public class MainActivity extends FragmentActivity {

        public static int NUM_PAGES = 0;
        public static ViewPager mPager;
        public static PagerAdapter mPagerAdapter;
        public static ContestEntriesModel Entries;
        public static HashMap<Long, android.support.v4.app.Fragment> mItems = new HashMap<Long, android.support.v4.app.Fragment>();
        public static Long CurrentEntryId;


        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.entries_layout);
            GetEntriesTask task = new GetEntriesTask(43);
            task.execute();
        }


        public void LoadEntries()
        {
            mPager = (ViewPager) findViewById(R.id.pager);
            mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager(), Entries);
            mPager.setAdapter(mPagerAdapter);
            mPager.setPageTransformer(true, new MyPageTransformer());
            mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                @Override
                public void onPageSelected(int position) {
                    ContestEntries.CurrentEntryId = ContestEntries.Entries.entries[position].ContestEntryId;
                    Log.d("currentEntry",Long.toString(ContestEntries.CurrentEntryId));
                    invalidateOptionsMenu();
                }
            });

        }
//the rest is omitted as it's not pertinent to my issue.

When a new dataload comes down from the sever, I am just setting my static property (Entries) to the new version. I also tried accessing the public property in the PagerAdapter (entries) to modify that directly, but that is not accessible for some reason (even though the class and the property both have a public accessor). I assume that is where this is going wrong because it's that instance that I need to be modifying, but honestly, my brain is fried at this point and I'm just not sure. Here is the code I'm using for the update:

UpdateCode:

@Override
protected void onPostExecute(ContestEntriesModel entries) {
    Fragment currentFragment = MainActivity.mItems.get(MainActivity.CurrentEntryId);
    Log.d("fromUpdate",Long.toString(MainActivity.CurrentEntryId));
    MainActivity.Entries = entries;
    MainActivity.NUM_PAGES = MainActivity.Entries.entries.length;
    //MainActivity.mItems.clear();
    MainActivity.mPagerAdapter.notifyDataSetChanged();
    MainActivity.mPager.setCurrentItem(MainActivity.mPagerAdapter.getItemPosition(currentFragment));
}

So using that, it does keep me on the current slide (which is what I need), but that slide is not repositioned correctly as mentioned above, nor are the other adjacent slides "refreshed". Again, assuming I'm on slide #3, if i swipe to the left once the old slide #2 is still there. If I swipe to the left twice, slide #1 is updated to the "new" slide (slide #7 from the first load). If I then swipe back to the right once, slide #2 is still there (should be slide #6). If I then swipe to the right once more, the "original" slide that I was on that I need maintained, is now replaced with slide #5 (again that number is from the original load). If I keep swiping to the right all the way to the "new" slide #5, I see the original again. That is where it needs to be on the new data load, in that position.

I sincerely apologize for this wall of text, but I thought if I could provide as much code as possible, this would make more sense. I've read several different approaches to this problem but I can't wrap my head around any that suit my needs specifically.

Any suggestions / code help would be greatly appreciated.

Christopher Johnson
  • 2,629
  • 7
  • 39
  • 70
  • See this [post](https://groups.google.com/forum/#!topic/android-developers/DbBrLvVfcSU). – AlexS Jun 18 '13 at 17:08

2 Answers2

1

You were working along the right lines. Your getItemPosition would never work because it is always instantiating a new Fragment object. You need to maintain your own ordering list and receive return POSITION_UNCHANGED if that objects view is still in the correct position. I will update my answer with a code example soon.

Simon
  • 10,932
  • 50
  • 49
  • ok thanks. I've been out of town for the last 12 days and just checked back on this. If you could lay out some code for me, it'd be much appreciated. – Christopher Johnson Jul 03 '13 at 20:42
  • I have code that let's you swap fragments with getItemPosition. I can post that in an hour or so, if you want. Or if you can wait a little longer I will post back a reorderable FragmentStatePagerAdapter which would probably solve your problem better. – Simon Jul 05 '13 at 18:57
  • 1
    Sorry this has taken so long.. here's your example. https://github.com/slightfoot/reorderable-pageradapter/tree/master/ReorderablePagerAdapter/src/com/demondevelopers/example/reorderablepageradapter – Simon Jul 07 '13 at 13:26
0

If all Fragments are of the same class you could just update the Fragments with new data intead oof reorder them.

MyFragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    DataProvider.getInstance().addOnDataChangedListener(this);
    if (mRoot == null) {
        mRoot = inflater.inflate(R.layout.entitlement, container, false);
    }

    updateDetails();
    return mRoot;
}

@Override
public void onDestroyView() {
    DataProvider.getInstance().removeOnDataChangeListener(this);
    super.onDestroyView();
}

public void dataChanged() {
    updateDetails();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    mContext = activity;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

The DataProvider simply calls dataChanged() on registered listeners. Important: If you use getActivity() getResource() or similar in updateDetails() this may crash, since the Activity may still be null although OnAttach() has been called. To avoid this I use my own Context-Object to do these things, which is set in OnAttach() and removed in OnDetach().

My adapter just takes care of the page titles, since the fragments refresh on their own:

protected LinkedList<CharSequence> mPageTitles = new LinkedList<CharSequence>();

@Override
public CharSequence getPageTitle(int position) {
    try {
        return mPageTitles.get(position);
    } catch (Exception ex) {
        return null;
    }
}

protected final void setData(List data) {
    mPageTitles.clear();
    if (list != null) {
        for (int idx = 0; idx < list.size(); ++idx) {
            mPageTitles.add(DataProvider.getInstance().getPageTitle(list.get(idx)));
        }
    }
}

@Override
public void notifyDataSetChanged() {
    setData(DataProvider.getInstance().getData());
    super.notifyDataSetChanged();
}

The time you update your DataProvider you also have to call myAdapter.notifyDataSetChanged().

This is probably not the best way of doing it, but you could give it a try. This solution actually produces a hiccup as it's goal is to display new data instantly.

If your currentSlide should not be updated, you should mark it and take care of it in updateDetails()`:

private boolean sticky = false;

public void setSticky() {
    sticky = true;
}

public void forceUpdate() {
    sticky = false;
    updateDetails();
}

protected void updateDetails() {
    if (sticky) {
        return;
    }
    // Do your update
}
AlexS
  • 5,295
  • 3
  • 38
  • 54
  • two questions about that approach: 1) I need the currently viewed fragment to maintain "focus" when the new load comes in from the server and it can't "update" as that would cause a hiccup, in usability for the user. All of the other slides can update in the background, but the currently viewed slide must not burp. Is that possible with this approach? This slide would need to be in the new position as well. 2) how would I force all of the other slides to "update" in the background. In my example above, slide 1 would turn to slide 7, 2 to 6, 3 to 5 etc. how can I force an update w/ new inf – Christopher Johnson Jun 18 '13 at 17:20
  • The update is done manually. The Fragments register themselfs for updates. This is dangerous though since it can cause memleaks. I don't know if the hiccup can be prevented, because in my app I want to update the current fragment as well. I will take a look tomorrow morning. .. – AlexS Jun 18 '13 at 21:17