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 DefaultStateViewPagerAdapter
code:
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
}