I have a "main" fragment that contains tabs on the side, and when I click on any of those tabs, the ViewPager2 of the "main" fragment jumps to the specific fragment (each fragment is of a different class) in the FragmentStateAdapter.
Let's say that for one of those fragments, it has a button where if pressed, it replaces the current fragment in the FragmentStateAdapter, but the tabs are still at the side (we are still in the "main" fragment"). If I click on any of the tabs, I want it to work as before where it goes to the specific fragment.
I want to do this but I'm not sure how to do it or if it is the correct approach. Should I replace one of the fragments in the FragmentStateAdapter, or should I just destroy the ViewPager2 altogether and replace it with that desired fragment? Or is there a better approach?
This is how that FragmentStateAdapter looks like and I want to replace the EscortFragment when a button in it is clicked, with a different fragment.
public class HomeTabAdapter extends FragmentStateAdapter {
private ArrayList<Fragment> fragments;
private Context context;
public HomeTabAdapter(#NonNull FragmentManager fragmentManager, #NonNull Lifecycle lifecycle, Context context) {
super(fragmentManager, lifecycle);
this.context = context;
fragments = new ArrayList<>();
fragments.add(new HomeFragment());
fragments.add(new PurposeOfVisitFragment());
fragments.add(new GalleryFragment());
fragments.add(new EscortFragment());
fragments.add(new MapsFragment());
fragments.add(new FaqFragment());
fragments.add(new SupportFragment());
}
#NonNull
#Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
#Override
public int getItemCount() {
return fragments.size();
}
}
Briefly, in your HomeTabAdapter class override getItemId() to generate a unique ID value for each of your fragments. Next time the base class FragmentStateAdapter needs to access the fragment, it will see a different ID value for that position. This will cause it to call createFragment() again for that position.
And if you override getItemId(), you must also override containsItem().
So, for example, add the following to your HomeTabAdapter class:
#Override
public long getItemId(int position) {
Fragment fragment = fragments.get(position);
long id = getIDForFragment(fragment);
return id;
}
#Override
public boolean containsItem(long itemId) {
for (Fragment fragment : fragments) {
if (getIDForFragment(fragment) == itemId)
return true;
}
return false;
}
private long getIDForFragment(Fragment fragment)
{
// Insert code that generates a unique ID value for each Fragment here.
}
public void replaceFragment(int index, Fragment fragment) {
fragments.set(index, fragment);
notifyDataSetChanged();
}
Notice that the method replaceFragment() calls notifyDataSetChanged(). This forces to check ID values immediately, rather than waiting until some later time.
I'm having some problems when dealing with Fragment using ViewPager.
What I'm having:
An activity (say, MainActivity) that contains a ViewPager to display some Fragment(s). Some of them contains a callback interface, which will be called to do somethings in the MainActivity.
The MainActivity has a FragmentPagerAdapter class, which is used as the adapter of the ViewPager. And a List<Fragment> in FragmentPagerAdapter to store some Fragment that will be displayed on the ViewPager.
What I'm expecting:
First launch, the Fragment called the callback interface's methods when I hit a button in it and MainActivity did somethings inside that. It worked great.
After a screen rotation, I expected it to work the same as the first launch BUT
NullPointerException: attempt to invoke a method on a null reference object (particularly, the Fragment's interface) hit me in my face.
What I know:
- The getItem(int position) won't be called again once the Fragment is created. Instead the instantiateItem(ViewGroup container, int position) will be called.
- FragmentManager will store some Fragment in mActive.mValues
- ViewPager and fragments — what's the right way to store fragment's state? (I did reference to this and some other same topics on StackOverflow too.)
What I have tried and saw:
- Override instantiateItem(ViewGroup container, int position)
- Debugged for 1 day. I saw that when I pass getSupportFragmentManager() in MainActivity's onCreate() method to FragmentPagerAdapter's super constructor, in the first launch, it has an "address in memory", assume it was '1a1a1a1'. The mActive.mValues of FragmentManager saved some Fragment' "address in memory" which are identical to the List<Fragment> containing them (assume it was 'qwertyu'). Which meaned it was right.
But when I rotated the screen, passing the getSupportFragmentManager() again, the "address in memory" was completely different, assume '9f9f9f9'. And FragmentManager's mActive.mValues contained a different set of Fragment' "address in memory" too (assume 'abcdeff'), although the number of Fragment in it was equal to the number of Fragment that was saved on the first launch (before rotation).
I have added a Fragment to the List<Fragment> with a new "address in memory" (assume 'abababa'), has the callback interface. But when I hit the button in it, it was the Fragment that was in the FragmentManager's mActive.mValues after the rotation (with "address in memory" is 'abcdeff' as I assumed above), and that one didn't have the callback interface (due to not being set in MainActivity first). And caused the NullPointerException as mentioned above.
My questions now is:
- First of all, how to get rid of this problem!? It would be better to keep using FragmentPagerAdapter instead of another class. But I will consider using other class too.
- Second, can you explain why FragmentManager saved the Fragment instance before rotation. But after rotation, it creates a completely different Fragment instance but still uses it instead of the Fragment that was saved in the List<Fragment>?
Here is the code (I think I didn't use the instantiateItem(ViewGroup container, int position) method in the right way so it still caused the problem).
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Attach the SectionsPagerAdapter to the ViewPager
SectionsPagerAdapter pagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(pagerAdapter);
}
//
//
//Adapter class
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private static final int PAGE_HOME = 0;
private int tabCount = 1;
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> fragmentTitleList = new ArrayList<>();
//private FragmentManager fragmentManager;
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
//fragmentManager = fm;
//Default HomeFragment
HomeFragment homeFragment = new HomeFragment();
//Callback interface
homeFragment.setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
//This method will be called when a button in HomeFragment is clicked
#Override
public void onAddNewCategory(String categoryName) {
addNewCategory(categoryName);
}
});
fragmentList.add(homeFragment);
fragmentTitleList.add("Home");
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public int getCount() {
return tabCount;
}
#Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
return fragmentList.get(position);
}
private void addNewCategory(String categoryName) {
CategoryFragment fragment = new CategoryFragment();
tabCount += 1;
fragmentList.add(fragment);
fragmentTitleList.add(categoryName);
notifyDataSetChanged();
}
}
}
Please help. I'm being insane for 2 days now...!
I believe android restarts the activity during orientation change thus making multiple instances of FragmentPagerAdapter and multiple set of instances of List.
I don't completely understand your question but I suspect instantiateItem doesn't do anything anyway. Doesn't the Fragment getItem(int pos) work without overriding instantiateItem()?
Oh well, right after i felt in sleep, I found the solution. It's true that I didn't use the instantiateItem() method in the right way. After debugging again, I found that the instantiateItem() method get call whenever I swipe (or choose if using TabLayout as well) to another Fragment, and even get call before getItem(int pos), no matter what it's the first launch or after rotation. Which is why I think we should set things up for the Fragment in the instantiateItem() method.
So here is how I use the instantiateItem() method now:
#Override
public Object instantiateItem(ViewGroup container, int position) {
fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
Fragment fragment = fragmentList.get(position);
if (position == PAGE_HOME) {
((HomeFragment) fragment).setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
#Override
public void onAddNewCategory(String categoryName) {
addNewCategory(categoryName);
}
});
}
return fragment;
}
If anyone have a better solution, please just tell me if you don't mind. I will consider about it.
When I start the app everything works ok but when I rotate to landscape it crashes because in the Fragment there is a field that is NULL.
I dont use setRetainInstance(true) or adding Fragments to FragmentManagerI create new Fragments on app start and when app rotate.
In the Activity OnCreate() I create the Fragment and adding them to the viewPager like this.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ParentBasicInfoFragment parentBasicInfoFragment = new ParentBasicInfoFragment();
ParentUTCFragment parentUTCFragment = new ParentUTCFragment();
ParentEventsFragment parentEventsFragment = new ParentEventsFragment();
this.mFragments = new ArrayList<>();
this.mFragments.add(parentBasicInfoFragment);
this.mFragments.add(parentUTCFragment);
this.mFragments.add(parentEventsFragment);
this.viewpage.setOffscreenPageLimit(3);
setCurrentTab(0);
this.viewpage.setAdapter(new MainActivityPagerAdapter(getSupportFragmentManager(), this.mFragments));
}
Then I have a test button on the app that when I press it will do like
public void test(View view) {
((BaseFragment) MainActivity.this.mFragments.get(MainActivity.this.viewpage.
getCurrentItem())).activityNotifiDataChange("hello");
}
This will work and the current Fragments in the ViewPager have the method, activityNotifiDataChange() that are being called and all is ok.
When I rotate the app and do the same thing pressing the button the activityNotifiDataChange() is being called alright but there a null pointer exception because the ArrayList<Fragment> mFragment is now NULL.
Here´s a small sample Android Studio project showing this behavior:
https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing
Start app and press button named "PRESS TEST", then rotate device and press the button again and watch the app crash
UPDATE SOLUTION thanks #GregMoens and #EpicPandaForce
public class MainActivityPagerAdapter extends PersistenPagerAdapter<BaseFragment> {
private static int NUM_ITEMS = 3;
public MainActivityPagerAdapter(FragmentManager fm) {
super(fm);
}
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ParentBasicInfoFragment.newInstance(0, "Page # 1");
case 1:
return ParentUTCFragment.newInstance(1, "Page # 2");
case 2:
return ParentEventsFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
}
public abstract class PersistenPagerAdapter<T extends BaseFragment> extends FragmentPagerAdapter {
private SparseArray<T> registeredFragments = new SparseArray<T>();
public PersistenPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public T instantiateItem(ViewGroup container, int position) {
T fragment = (T)super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public T getRegisteredFragment(ViewGroup container, int position) {
T existingInstance = registeredFragments.get(position);
if (existingInstance != null) {
return existingInstance;
} else {
return instantiateItem(container, position);
}
}
}
The main problem I see with your app is your misunderstanding with how FragmentPagerAdapter works. I see this a lot and it's due to lack of good javadocs on the class. The adapter should be implemented so that getItem(position) returns a new fragment instance when called. And then getItem(position) will only be called by the pager when it needs a new instance for that position. You should not pre-create the fragments and pass then into the adapter. You should also not be holding strong references to the fragments from either your activity or from parent fragments (like ParentBasicInfoFragment). Because remember, the fragment manager is managing fragments and you are also managing fragments by newing them and keeping references to them. This is causing a conflict and after rotation, you are trying to invoke activityNotifiDataChange() on a fragment that is not actually initialized (onCreate() was not called). Using the debugger and tracking object IDs will confirm this.
If you change your code so that the FragmentPagerAdapter creates the fragments when they are needed and don't store references to fragments or lists of fragments, you will see much better results.
this.mFragments = new ArrayList<>();
Because this is wrong. You should never hold a reference to the fragment list if you are using ViewPager. Just return new instance of Fragment from getItem(int position) of FragmentPagerAdapter and you are good to go.
But to fix your code, you must delete mFragments entirely.
See https://stackoverflow.com/a/58605339/2413303 for more details.
Use setRetainInstance(true) is not a good approach. If you need to same some simple information such as: position of recyclerView, selected item of recyclerView, maybe some model(Parcelable) you could do it with method onSaveInstanceState / onRestoreInstanceState, there is one limitation is 1MB. Here is an example with animations how it works.
For more durable persistance use SharedPreferences, Room(Google ORM) or you could try to use ViewModel with LiveData (best approach some data which should live while user session).
//change in AndroidManifest.xml file,
<activity
android:name=".activity.YourActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="sensor"
/>
//YourActivity in which you defines fragment.
//may be it helps
I'm facing some issues with ViewPager and Fragment's instances.
I have a ViewPager (let's call Father) with 4 fragments and into the last fragment I have another ViewPager (and call it Child) with dynamic fragments amount. What I mean is that I create the Child based on a list of objects in memory. So if the list contains 3 objets, the Child will have 3 fragments inside. If in a determinate moment something happens and I get a list with 1 object, then I must update the Child with just 1 fragment. An important point in here is that each Child'sFragment has its own object from the returned list and is created based on this object.
The code I do to set the list of fragments into the Child ViewPager is the following:
#Override
public void setViewPagerChildFragments(List<Fragment> fragments) {
if (fragments != null) {
DefaultStateViewPagerAdapter adapter = (DefaultStateViewPagerAdapter) mViewpagerChild.getAdapter();
if (adapter == null) {
/* In this case I use getChildFragmentManager() because
it's inside the last fragment of the ViewPager Father*/
adapter = new DefaultStateViewPagerAdapter(getChildFragmentManager(), fragments);
mViewpagerChild.setAdapter(adapter);
} else {
adapter.setFragments(fragments); // Into this method I do notifyDataSetChanged() already
}
}
}
Note that I try to use the same adapter's instance to set the fragments and then notify the changes (notifyDataSetChanged() is inside the method). If I don't get the adapter's instance, I create a new one and set it to the ViewPager Child.
The problem happens, for example, when I set the ViewPager Child with 2 fragments and after a while I need to set it with 1 fragment. The ViewPager shows just 1 fragment inside it, but the second one is still attached and isn't destroyed. I know it because I did a test calling getChildFragmentManager().getFragments(), and I could see the fragment which was supposed to be destroyed is still there.
You may say it isn't actually a problem, since the Garbage Collector can remove the unused Fragment. However, if in some moment for example, I try to set 2 new fragments again into the ViewPager Child, it uses that unused Fragment instance instead of creating a new one and unfortunately it also uses its same object, and not the right new one.
This is my DefaultStateViewPagerAdaptercode:
public class DefaultStateViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> mFragments;
private ArrayList<String> mFragmentTitles;
public DefaultStateViewPagerAdapter(FragmentManager fm) {
super(fm);
this.mFragments = new ArrayList<>();
this.mFragmentTitles = new ArrayList<>();
}
public DefaultStateViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.mFragments = (ArrayList<Fragment>) fragments;
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getItemPosition(Object object) {
int index = mFragments.indexOf(object);
if (index < 0) {
index = POSITION_NONE;
}
return index;
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
public void clearFragments() {
mFragments.clear();
mFragmentTitles.clear();
}
public void setFragments(List<Fragment> fragments) {
mFragments = (ArrayList<Fragment>) fragments;
notifyDataSetChanged();
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
}
I already tried to override saveState in order to avoid the ViewPager uses the old fragment´s instance, like:
#Override
public Parcelable saveState() {
return null;
}
It worked, and the ViewPager no longer uses the old reference, but the unused Fragment is still attached, and it causes memory leak.
I don't know why the ViewPager doesn't destroy its fragments even after I set a new adapter. Has anyone ever had this issue?
I found the solution.
The solution is very simple. I just had to set manually null to the Child's adapter. With this, the ViewPager is forced to destroy every fragment.
So into the onDestroyView of Fragment's father I added:
#Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}
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
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.
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
}
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
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.
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.
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.
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.
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's onItemPosition() will be called before notifyDataSetChanged() returns. Nothing needs to be done in getItemPosition(). Then the data is updated.
Try destroyDrawingCache() on ViewPager after notifyDataSetChanged() in your code.
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.
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) {}
}
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)
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...
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
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));
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;
I know am late for the Party. I've fixed the problem by calling TabLayout#setupWithViewPager(myViewPager); just after FragmentPagerAdapter#notifyDataSetChanged();
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.
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()
#Override
public void notifyDataSetChanged() {
mFragments.clear();
super.notifyDataSetChanged();
}
Add a sanity check to destroyItem() to prevent crashes:
if (position < mFragments.size()) {
mFragments.set(position, null);
}
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);
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:
Set up FragmentStateAdapter and override the following methods: getItemCount, createFragment, getItemId, and containsItem (note: FragmentStatePagerAdapter is not supported by ViewPager2)
Attach adapter to ViewPager2
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!!)
}
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.
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);
}
}
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;
}
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