Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly handle screen rotation with a ViewPager and nested fragments?

I've got this activity, which holds a fragment. This fragment layout consists of a view pager with several fragments (two, actually).

When the view pager is created, its adapter is created, getItem gets called and my sub fragments are created. Great.

Now when I rotate the screen, the framework handles the fragment re-creation, the adapter is created again in my onCreate from the main fragment, but getItem never gets called, so my adapter holds wrong references (actually nulls) instead of the two fragments.

What I have found is that the fragment manager (that is, the child fragment manager) contains an array of fragments called mActive, which is of course not accessible from code. However there's this getFragment method:

@Override public Fragment getFragment(Bundle bundle, String key) {     int index = bundle.getInt(key, -1);     if (index == -1) {         return null;     }     if (index >= mActive.size()) {         throwException(new IllegalStateException("Fragement no longer exists for key "                 + key + ": index " + index));     }     Fragment f = mActive.get(index);     if (f == null) {         throwException(new IllegalStateException("Fragement no longer exists for key "                 + key + ": index " + index));     }     return f; } 

I won't comment the typo :)
This is the hack I have implemented in order to update the references to my fragments, in my adapter constructor:

// fm holds a reference to a FragmentManager Bundle hack = new Bundle(); try {     for (int i = 0; i < mFragments.length; i++) {         hack.putInt("hack", i);         mFragments[i] = fm.getFragment(hack, "hack");     } } catch (Exception e) {     // No need to fail here, likely because it's the first creation and mActive is empty } 

I am not proud. This works, but it's ugly. What's the actual way of having a valid adapter after a screen rotation?

PS: here's the full code

like image 346
Benoit Duffez Avatar asked Oct 15 '13 23:10

Benoit Duffez


People also ask

What is difference between ViewPager and ViewPager2?

ViewPager2 is an improved version of the ViewPager library that offers enhanced functionality and addresses common difficulties with using ViewPager . If your app already uses ViewPager , read this page to learn more about migrating to ViewPager2 .

When should I use ViewPager?

Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows. ViewPager is most often used in conjunction with android.

What is ViewPager2 in android?

ViewPager2 uses FragmentStateAdapter objects as a supply for new pages to display, so the FragmentStateAdapter will use the fragment class that you created earlier. Create an activity that does the following things: Sets the content view to be the layout with the ViewPager2 .


2 Answers

I had the same issue - I assume you're subclassing FragmentPagerAdapter for your pager adapter (as getItem() is specific to FragmentPagerAdapter).

My solution was to instead subclass PagerAdapter and handle the fragment creation/deletion yourself (reimplementing some of the FragmentPagerAdapter code):

public class ListPagerAdapter extends PagerAdapter {     FragmentManager fragmentManager;     Fragment[] fragments;      public ListPagerAdapter(FragmentManager fm){         fragmentManager = fm;         fragments = new Fragment[5];     }      @Override     public void destroyItem(ViewGroup container, int position, Object object) {         assert(0 <= position && position < fragments.length);         FragmentTransaction trans = fragmentManager.beginTransaction();         trans.remove(fragments[position]);         trans.commit();         fragments[position] = null; }      @Override     public Fragment instantiateItem(ViewGroup container, int position){         Fragment fragment = getItem(position);         FragmentTransaction trans = fragmentManager.beginTransaction();         trans.add(container.getId(),fragment,"fragment:"+position);         trans.commit();         return fragment;     }      @Override     public int getCount() {         return fragments.length;     }      @Override     public boolean isViewFromObject(View view, Object fragment) {         return ((Fragment) fragment).getView() == view;     }      public Fragment getItem(int position){         assert(0 <= position && position < fragments.length);         if(fragments[position] == null){             fragments[position] = ; //make your fragment here         }         return fragments[position];     } } 

Hope this helps.

like image 183
Josh Hunt Avatar answered Sep 21 '22 17:09

Josh Hunt


My answer is kind of similar to Joshua Hunt's one, but by commiting the transaction in finishUpdate method you get much better performance. One transaction instead of two per update. Here is the code:

private class SuchPagerAdapter extends PagerAdapter{      private final FragmentManager mFragmentManager;     private SparseArray<Fragment> mFragments;     private FragmentTransaction mCurTransaction;      private SuchPagerAdapter(FragmentManager fragmentManager) {         mFragmentManager = fragmentManager;         mFragments = new SparseArray<>();     }      @Override     public Object instantiateItem(ViewGroup container, int position) {         Fragment fragment = getItem(position);         if (mCurTransaction == null) {             mCurTransaction = mFragmentManager.beginTransaction();         }         mCurTransaction.add(container.getId(),fragment,"fragment:"+position);         return fragment;     }      @Override     public void destroyItem(ViewGroup container, int position, Object object) {         if (mCurTransaction == null) {             mCurTransaction = mFragmentManager.beginTransaction();         }         mCurTransaction.detach(mFragments.get(position));         mFragments.remove(position);     }      @Override     public boolean isViewFromObject(View view, Object fragment) {         return ((Fragment) fragment).getView() == view;     }      public Fragment getItem(int position) {                  return YoursVeryFragment.instantiate();     }      @Override     public void finishUpdate(ViewGroup container) {         if (mCurTransaction != null) {             mCurTransaction.commitAllowingStateLoss();             mCurTransaction = null;             mFragmentManager.executePendingTransactions();         }     }       @Override     public int getCount() {         return countOfPages;     }  } 
like image 45
simekadam Avatar answered Sep 21 '22 17:09

simekadam