Question

In portrait mode, my ViewPager has 3 fragments A, B, C but in landscape mode, it has only 2 fragments A and C. So I create 2 FragmentStatePagerAdapters for each mode. The problem is when screen orientation changed, ViewPager restores and uses previous fragments of old orientation. For example, when change orientation from portrait to landscape, ViewPager now shows 2 fragments A, B instead of A and C. I know why this happen but can't find a good solution for this.

My current workaround is to use different ids for ViewPager (eg: id/viewpager_portrait for portrait and id/viewpager_landscape for landscape layout) to prevent from reusing fragments but this cause me a memory leak because old fragment will not be destroyed and still be kept in memory.

I have tried some workaround like call super.onCreate(null) in activity's onCreate, or remove fragments of ViewPager in activity's onSaveInstanceState but they all makes my app crash.

So my question is how to avoid reusing one or many fragments in FragmentStatePagerAdapter when orientation changed?

Any helps will be appreciated. Thank in advance.

Was it helpful?

Solution

The issue probably is that the built-in PagerAdapter implementations for Fragments provided by Android assume that the items will remain constant, and so retain and reuse index-based references to all Fragments that are added to the ViewPager. These references are maintained through the FragmentManager even after the Activity (and Fragments) is recreated due to configuration changes or the process being killed.

What you need to do is to write your own implementation of PagerAdapter that associates a custom tag with each Fragment and stores the Fragments in a tag-based (instead of index-based) format. You could derive a generic implementation of this from one of the existing ones after adding an abstract method for providing a tag based on the index alongside the getItem() method. Of course, you will have to remove orphaned/unused Fragments added in the previous configuration from the ViewPager (while ideally holding on to it's state).

If you don't want to implement the whole solution yourself, then the ArrayPagerAdapter in the CWAC-Pager library can be used to provide a reasonable implementation of this with little effort. Upon initialization, you can detach the relevant Fragment based on it's provided tag, and remove/add it from the adapter as well, as appropriate.

OTHER TIPS

Override getItemPosition() in your Adapter and return POSITION_NONE. So when the ViewPager is recreated it will call getItemPosition() and since you've returned POSITION_NONE from here, it will call getItem(). You should return the new fragments in from this getItem(). Ref: http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getItemPosition%28java.lang.Object%29

Why use two different ids for your viewpager, when you can just remove Fragment B when your orientation changes?

You can retrieve your Fragments inside onCreateView() or onResume() like this (this example works inside a parent fragment, but is also usable inside a parent activity like in onResume() ):

@Override
public void onResume() {
    super.onResume();

    // ... initialize components, etc.

    pager.setAdapter(pagerAdapter);

    List<Fragment> children = getChildFragmentManager().getFragments();

    if (children != null) {
        pagerAdapter.restoreFragments(children, orientation);
    }
}

Then inside your adapter:

@Override
public void restoreFragments(List<Fragment> fragments, int orientation) {
    List<Fragment> fragmentsToAdd = new ArrayList<Fragment>();
    Collections.fill(fragmentsToAdd, null);
    if (Configuration.ORIENTATION_LANDSCAPE == orientation) {
        for (Fragment f : fragments) {
            if (!(f instanceof FragmentB)) {
                fragmentsToAdd.add(f);
            }
        }
    }
    this.fragmentsInAdapter = Arrays.copyOf(temp.toArray(new Fragment[0]), this.fragments.length); // array of all your fragments in your adapter (always refresh them, when config changes, else you have old references in your array!
    notifyDataSetChanged(); // notify, to remove FragmentB
}

That should work.

Btw. if you're using Support Library V13, you can't use FragmentManager.getFragments(), thus you'll need to get them by id or tag.

Override OnConfigurationChanged() in your Activity (also add android:configChanges="orientation" to your activity in Manifest) this way you manage manually the the orientation change. Now "all" you have to do is to change the adapter in OnOrientaionChanged (also keep track of the current position). This way you use a single layout, a single ViewPager, and you don't have to worry about the fragment not getting recycled (well, you'll have plenty of work to make up for that). Good luck!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top