Search code examples
androidandroid-fragmentsandroid-viewpagerfragmentstatepageradapter

ViewPager holds fragment's instance even after setting a new adapter


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?


Solution

  • 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
    }