ViewPager with different adapters for portrait and landscape
Asked Answered
L

4

6

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.

Lixivium answered 18/4, 2014 at 7:51 Comment(2)
Did you try overriding onSaveInstanceState and avoid calling super.onSaveInstanceState in that method. This would prevent the Fragment from getting saved.Araroba
That is my current workaround, but instead avoid calling super.onSaveInstanceState, I clear all state made by ViewPager. And I don't think this a the best solution so I still ask because sometime a see a crash from inside ViewPager.Lixivium
R
3

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.

Reamer answered 31/5, 2014 at 7:43 Comment(3)
I know that write my own adapter will solve problem but I think there is a better and easier solution than that so I ask on StackOverflow.Lixivium
@Wayne: I have added some more information to the end of the answer, and also a suggestion to use the CWAC-Pager library.Reamer
Thank you corsair992, my solution is to copy PagerAdapter source and change a bit of code base on your answer. I'm sorry, I was not online last weekend so I forgot to give full bounty for you/Lixivium
A
1

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

Ariannearianrhod answered 5/6, 2014 at 10:21 Comment(2)
Oh I forgot about getItemPosition(). Maybe this will be a good solution. ThanksLixivium
This answer is not correct. ViewPager only looks for changes in item positions upon calls to notifyDataSetChanged() on the PagerAdapter, and this would not work properly for removing an item from a FragmentStatePagerAdapter anyway because it assumes the items to be immutable and it would mess up it's saved state associations. Not would it work for FragmentPagerAdapter, as it assumes the same thing, and will just reuse the original Fragment.Reamer
A
1

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.

Allantois answered 6/6, 2014 at 11:10 Comment(0)
L
1

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!

Luhey answered 6/6, 2014 at 11:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.