I can't update the content in ViewPager.
A question: What is the relationship and 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 (but as said I can't change the content).
So I found this: ViewPager PagerAdapter not updating the View
Particulary one in which it's talked about method instantiateItem():
"My approach is to use the setTag() method for any instantiated view in the instantiateItem() method"
So now I want to implement instantiateItem() in order to do that. But I don't know what I have to return there (return is Object) and what is the relation with getItem(int position)?
Currently I'm using getItem to instantiate the Fragment, is that wrong? But then do I have to put the fragments in instance variable, or not implement getItem() at all...? I just don't understand it.
I tried reading 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 understand how are they related and what I have to do.
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
Instead of returning
POSITION_NONE
fromgetItemPosition()
and causing full view recreation, do this:Your fragments should implement
UpdateableFragment
interface:and the interface:
Your data class:
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.
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).
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.
Copy the source for for FragmentStatePagerAdapter (seems to have not been updated in ages).
Override notifyDataSetChanged()
Add a sanity check to destroyItem() to prevent crashes:
When using FragmentPagerAdapter or FragmentStatePagerAdapter, it is best to deal solely with
getItem()
and not touchinstantiateItem()
at all. TheinstantiateItem()
-destroyItem()
-isViewFromObject()
interface on PagerAdapter is a lower-level interface that FragmentPagerAdapter uses to implement the much simplergetItem()
interface.Before getting into this, I should clarify that
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()
andfindViewWithTag()
workaround provided in the post you linked. As you've discovered, usingsetTag()
andfindViewWithTag()
doesn't work with fragments, so it's not a good match.The right solution is to override
getItemPosition()
. WhennotifyDataSetChanged()
is called, ViewPager callsgetItemPosition()
on all the items in its adapter to see whether they need to be moved to a different position or removed.By default,
getItemPosition()
returnsPOSITION_UNCHANGED
, which means, "This object is fine where it is, don't destroy or remove it." ReturningPOSITION_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: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 inonDestroy()
, thus managing the updates itself. You don't have to put all the update code insidegetItem()
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.
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 usingFragmentStatePagerAdapter
in API level 17 and currently have just 2 fragments. I think there must be something not correct, please correct me, thanks.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 aCursorLoader
, it's auto-loaded since there is a registered data observer.After you call
viewpager.setAdapter(pageradapter)
, the adapter'sgetCount()
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.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 bygetCount()
, then call the adapter'snotifyDataSetChanged()
.ViewPager
begin to create fragments (just the first 2 fragments) by data in memory. It's done beforenotifyDataSetChanged()
is returned. Then theViewPager
has the right fragments you need.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.So when data in memory is updated, we just need to call the adapter's
notifyDataSetChanged()
. Since the fragment is already created, the adapter'sonItemPosition()
will be called beforenotifyDataSetChanged()
returns. Nothing needs to be done ingetItemPosition()
. Then the data is updated.