12

In my application the fragment activity holds two fragments, Fragment A and Fragment B. Fragment B is a view pager that contains 3 fragments.

enter image description here

In my activity, to prevent that the fragment is recreated on config changes:

if(getSupportFragmentManager().findFragmentByTag(MAIN_TAB_FRAGMENT) == null) {
    getSupportFragmentManager().beginTransaction().replace(R.id.container, new MainTabFragment(), MAIN_TAB_FRAGMENT).commit();
}

Code for Fragment B:

public class MainTabFragment extends Fragment {

    private PagerSlidingTabStrip mSlidingTabLayout;
    private LfPagerAdapter adapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_tab, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {

        this.adapter = new LfPagerAdapter(getChildFragmentManager());

        this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
        this.mViewPager.setAdapter(adapter);

        this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
        this.mSlidingTabLayout.setViewPager(this.mViewPager);
    }
}

Code for the adapter:

public class LfPagerAdapter extends FragmentPagerAdapter {

    private static final int NUM_ITEMS = 3;

    private FragmentManager fragmentManager;

    public LfPagerAdapter(FragmentManager fm) {
        super(fm);
        this.fragmentManager = fm;
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public Fragment getItem(int position) {
        Log.d("TEST","TEST");
        switch (position) {
            case 1:
                return FragmentC.newInstance();
            case 2:
                return FragmentD.newInstance();
            default:
                return FragmentE.newInstance();
        }
    }
}

My problem is that I am not able to retain the state of the view pager an its child fragments on orientation changes.

Obviously this is called on every rotation:

this.adapter = new LfPagerAdapter(getChildFragmentManager());

which will cause the whole pager to be recreated, right? As a result

getItem(int position)

will be called on every rotation and the fragment will be created from scratch and losing his state:

return FragmentC.newInstance();

I tried solving this with:

if(this.adapter == null)
    this.adapter = new LfPagerAdapter(getChildFragmentManager());

in onViewCreated but the result was that on rotation the fragments inside the pager where removed.

Any ideas how to correctly retain the state inside the pager?

DarkLeafyGreen
  • 69,338
  • 131
  • 383
  • 601
  • I did this by saving the fragment state (and the selected pager position) in the instanceState (don't know the exact override since I don't have access to Eclipse now). Then on the OnCreate (if in activity) or OnCreateView() (if in fragment), I check if they exist and then recreate them. – kha Dec 05 '14 at 09:18
  • `Fragment`s are automatically managed and restored by the `FragmentManager`, and the `PagerAdapter` implementations are designed to keep a persistent reference to them. The `getItem()` will only be called once for initializing each page. – corsair992 Dec 10 '14 at 10:38
  • @artworkad: It seems that `Fragments` are retained already. what exactly do you want to perform? can you describe little more? – Mehul Joisar Dec 10 '14 at 14:20

2 Answers2

31

You will need to do 2 things to resolve the issue:

1) You should use onCreate method instead of onViewCreated to instantiate LfPagerAdapter;

i.e.:

    public class MainTabFragment extends Fragment {

    private PagerSlidingTabStrip mSlidingTabLayout;
    private LfPagerAdapter adapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        this.adapter = new LfPagerAdapter(getChildFragmentManager());
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_tab, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {


        this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
        this.mViewPager.setAdapter(adapter);

        this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
        this.mSlidingTabLayout.setViewPager(this.mViewPager);
    }
}

2) You will need to extend FragmentStatePagerAdapter instead of FragmentPagerAdapter

Mehul Joisar
  • 15,348
  • 6
  • 48
  • 57
  • `FragmentPagerAdapter` never detaches the `Fragment`s so they are automatically managed and retained by the `FragmentManager` as usual, and both of the adapters keep a persistent reference to them. Retaining the `Fragment` instance is not necessary, and in fact might even interfere in some ways with the semantics of retaining the states of the `View`s and/or child `Fragment`s. – corsair992 Dec 10 '14 at 12:37
  • @corsair992: then what is required to do to solve the problem? – Mehul Joisar Dec 10 '14 at 13:26
  • Nothing - there is no problem to begin with. If the `Fragment` instance is not retained, then the state of the `ViewPager` and the child `Fragment`s should already be managed (although you might need to set it up properly to retain the current page). – corsair992 Dec 10 '14 at 13:58
  • @corsair992: not retained or retained? I didn't get your point. – Mehul Joisar Dec 10 '14 at 14:18
  • As I said in my first comment: "Retaining the `Fragment` instance is not necessary, and in fact might even interfere in some ways with the semantics of retaining the states of the `View`s and/or child `Fragment`s.". I am talking about the `setRetainInstance(true);` call in the `Fragment` initialization code. – corsair992 Dec 10 '14 at 14:23
  • @corsair992: so I guess if we use `setRetainInstance(true)` for `FragmentC`,`FragmentD`,`FragmentE` then also problem can be solved. right? – Mehul Joisar Dec 10 '14 at 14:30
  • It would be better to not do it altogether, as there is no need for it, and it's not meant to be done for a UI `Fragment` in any case. – corsair992 Dec 10 '14 at 14:32
  • Thank you for the info & comments. Actually I assumed that the fragments will be retained, however it was not the case because getItem was called multiple times. Going to investigate this soon if I have time. I leave this question open as soon as I tested this it. – DarkLeafyGreen Dec 11 '14 at 16:45
  • @artworkadシ: As I mentioned, you should remove the `setRetainInstance(true)` from your base `Fragment`, and then it should work as expected. – corsair992 Dec 13 '14 at 01:59
  • @corsair it does not work like u said. If setRetainInstance is removed from base fragment then all child fragments in adapter are recreated – Ravi Feb 06 '15 at 12:50
  • @Ravi: I did not claim that they wouldn't be recreated. They would be managed like any other `Fragment`. – corsair992 Feb 06 '15 at 15:06
1

Android will automatically recreate your activity on configuration without this line android:configChanges="keyboardHidden|orientation|screenSize" in you manifest so that you can handle onConfiguration change yourself.

The only way then is to use onSaveInstanceState() in both your activityFragement to save viewPager state(current position for example) and in fragments where you need to save stuff

Example on how you can save current position of viewpager and restore it onConfiguration change

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
            int position = mViewPager.getCurrentItem();
            outState.Int("Key", position );
    }

    @Override //then restore in on onCreate();
    public void onCreated(Bundle savedInstanceState) {
        super.onCreated(savedInstanceState);
        // do stuff
        if (savedInstanceState != null) {
              int  position= savedInstanceState.getInt("Key");
            mViewPager.setCurrentItem(position)
        }
    }

Of course, this is a very basic example.

Ps: to restore in fragment use onActivityCreated() instead of onCreate() method.

Here is another example on how to retain state : Click me!

Mohamed ALOUANE
  • 5,349
  • 6
  • 29
  • 60
murielK
  • 1,000
  • 1
  • 10
  • 21
  • Actually, `ViewPager` manages the saving and restoring of the current position itself, if the adapter is set at the proper time after the `View` states are restored. – corsair992 Dec 13 '14 at 01:30
  • I know this was a while ago, but @corsair992 could you expand on this? It sounds like exactly what I need. – Steven Schoen Apr 06 '15 at 02:07
  • @D_Steve595: Taking a look at the source code of `ViewPager` in the latest version of the support library, it seems that it should now properly handle persisting the position and adapter state in all cases. Are you having any issue with this? – corsair992 Apr 06 '15 at 09:11
  • Thanks for the response! I ended up making a question and it was answered here: http://stackoverflow.com/questions/29464522/how-do-i-let-views-keep-their-states-in-a-fragment – Steven Schoen Apr 06 '15 at 14:52