49

I tried out the sample code from the API and it didn't really work so I implemented my own:

FragmentPagerSupport

public class FragmentPagerSupport extends FragmentActivity {

static final int NUM_ITEMS = 10;
MyAdapter mAdapter;
ViewPager mPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mAdapter = new MyAdapter(getSupportFragmentManager());
    Log.i("Pager", "mAdapter = " + mAdapter.toString());

    mPager = (ViewPager)findViewById(R.id.pager);
    if (mPager == null)
        Log.i("Pager", "mPager = null");
    else 
        Log.i("Pager", "mPager = " + mPager.toString());

    Log.i("Pager", "Setting Pager Adapter");
    mPager.setAdapter(mAdapter);
}

public static class MyAdapter extends FragmentPagerAdapter {
    public MyAdapter(FragmentManager fm) {
        super(fm);
        Log.i("Pager", "MyAdapter constructor");
    }

    @Override
    public int getCount() {
        Log.i("Pager", "MyAdapter.getCount()");
        return NUM_ITEMS;            
    }

    @Override
    public Fragment getItem(int position) {
        Log.i("Pager", "MyAdapter.getItem()");

        return TestFragment.newInstance(position);
    }
}

public static class TestFragment extends Fragment {

    public static TestFragment newInstance(int position) {
        Log.i("Pager", "TestFragment.newInstance()");

        TestFragment fragment = new TestFragment();

        Bundle args = new Bundle();
        args.putInt("position", position);
        fragment.setArguments(args);

        return fragment;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        Log.i("Pager", "TestFragment.onCreateView()");

        LinearLayout layout = (LinearLayout)inflater.inflate(R.layout.fragment_item, null);
        int position = getArguments().getInt("position");

        TextView tv = (TextView)layout.findViewById(R.id.text);
        tv.setText("Fragment # " + position);
        tv.setTextColor(Color.WHITE);
        tv.setTextSize(30);

        return layout;
    }

}

}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <android.support.v4.view.ViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</LinearLayout>

fragment_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  
    android:id="@+id/text"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    android:gravity="center" 
    android:text="@string/hello"
    />
</LinearLayout>

My question is, instead of creating a new Fragment instance each time the user swipes left and right, how do I save a fragment's state (to some data structure) and then restore it?

The API demo doesn't seem to have any state information saving code at all.

Andrew T.
  • 4,701
  • 8
  • 43
  • 62
Android Noob
  • 501
  • 1
  • 5
  • 5
  • Check out my answer at: http://stackoverflow.com/questions/6908000/android-viewpager-and-fragmentstatepageadapter/7030271#7030271 – rui.araujo Aug 11 '11 at 17:35
  • 1
    You place the `Fragment`s in a data structure in the first place and write the `getItem` method so it accesses the correct fragment. Say your fragments are in an `ArrayList fragList`, you just have the `getItem` method `return fragList.get(position)`. Simple and no need to break the `ViewPager` by screwing with `destroyItem`. – dcow Oct 05 '12 at 18:13

6 Answers6

127

Use ViewPager.setOffscreenPageLimit() in FragmentActivity

ViewPager pager = (ViewPager) findViewById(R.id.viewPager);
pager.setOffscreenPageLimit(2);

See more at How do I tell my custom FragmentPagerAdapter to stop destroying my fragments?

Community
  • 1
  • 1
Thein
  • 3,940
  • 2
  • 30
  • 34
  • 7
    Easy and effective solution for non dynamic view pagers, because you you will keep the state of the fragment without doing anything more. Recomended if you need not too much memory resources. – Pelanes Jan 15 '14 at 20:06
  • @Pelanes what do you mean by `non dynamic view pagers` thanks! ? – marson Feb 01 '14 at 15:24
  • ViewPagers with a fixed numbers of pages, where users (or automatically the app) add/remove pages. – Pelanes Feb 03 '14 at 18:17
  • This is great, I've been searching for hours attempting to save the instance of my fragment. But this solution is so much simpler, when there isn't a large amount of data to be stored in memory on each fragment – JayDev Nov 11 '14 at 22:29
  • this Should be accepted as Correct answer. so that users find this solution easily thanks! – Imran Ahmed Feb 06 '15 at 18:58
  • 10
    Of course this is not a fix since it won't help save state properly. It just prevents the Pager from destroying unused Fragments, effectively pushing the problem to a later time. For example to when to rotate the device or pause/resume the app. – Stephan Henningsen Mar 16 '16 at 12:29
  • 4
    This doesn't answer the question. You only provided a work around. – Michael Fulton Jul 03 '16 at 01:27
  • Works for me! Thanks. – Hadi Note Sep 06 '17 at 07:10
24

just override this method in FragmentpagerAdapter

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            // TODO Auto-generated method stub
            super.destroyItem(ViewGroup container, int position, Object object);
        }

remove super.destroyItem(ViewGroup container, int position, Object object);

from your code

Jinu
  • 8,665
  • 4
  • 32
  • 37
15

Update: In the getItem of MyAdapter you return a new instance of your Fragment. Android will not call getItem on every swipe. It will only call getItem to get a new instance, but will reuse the existing instances as long as they are availible.

About the state part of the Demo. I can't help you. I think the normal techniques for restoring state in Fragements/Activities apply here, so nothing special when loading it in a ViewPager (but I might be wrong).

Jeroen
  • 3,399
  • 1
  • 22
  • 25
14

If you override

public void destroyItem(View container, int position, Object object)

without calling super, the fragment will not be destroyed and will be reused.

Marco
  • 205
  • 2
  • 3
  • 1
    Just a quick note: this method should be added in the adapter (ie. the class that extends FragmentPagerAdapter). – Igor Popov Jan 01 '12 at 09:45
  • 16
    This is not the way you should go about solving the problem and is really poor design. Please don't do this and rather take a few moments to understand what the problem really is and the extremely trivial solution to it detailed in @jtietema's answer. – dcow Oct 05 '12 at 18:06
  • 1
    What @DavidCowden said. This is a really bad hack. – Adam Dec 04 '12 at 05:54
  • That method was deprecated. You must use: @Override public void destroyItem(ViewGroup container, int position, Object object) { } – hector6872 Jun 09 '13 at 22:20
  • Again: No, you must *not* use destroyItem() – Stephan Henningsen Mar 16 '16 at 12:24
  • Perfect Answer. There wasn't really need to kill fragments in my case. – TaRan LaYal Aug 04 '17 at 07:30
4

The adapter should be extended from FragmentStatePagerAdapter instead of FragmentPageAdapter and the adapter will keep reference from position to fragment item. On the activity or fragment parent, set listener OnPageChangeListener for PageIndicator to detect the position of fragment item has been activated and update related data's state.

About the data state of fragment item, I think should save/restore state from Activity or Fragement parent.

The adapter can add some code as following:

public static class MyAdapter extends FragmentStatePagerAdapter {
    private SparseArray<TestFragment> mPageReferenceMap 
                              = new SparseArray<TestFragment>();
    ...

    @Override 
    public Object instantiateItem(ViewGroup viewGroup, int position) {
        Object obj = super.instantiateItem(viewGroup, position);

        //Add the reference when fragment has been create or restore
        if (obj instanceof TestFragment) {
                TestFragment f= (TestFragment)obj;
                mPageReferenceMap.put(position, f);
        }

        return obj;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        //Remove the reference when destroy it
        mPageReferenceMap.remove(position);

        super.destroyItem(container, position, object);
    }

    public TestFragment getFragment(int key) {

        return mPageReferenceMap.get(key);
    }
    ...
}

Hope this help.

ThanhHH
  • 6,560
  • 1
  • 19
  • 22
0

I tripped on some similar ground... I wanted to keep my fragments saved within my adapter to avoid useless reloading since I only had 5 fragments in the FragmentPagerAdapter.

I basically had to create an array and override the getItem(position) method:

private StoryListFragment[] fragmentsArray
        = new StoryListFragment[getCount()];

public SectionsAdapter(FragmentManager fm) {
    super(fm);
}


@Override
public Fragment getItem(int position) {

    if(fragmentsArray[position]!=null)
        return fragmentsArray[position];
    StoryListFragment storyListFragment = null;
    switch(position){
        case(0):
            storyListFragment = StoryListFragment.newInstance(StoriesBank.NEWS);
            break;
        case(1):
            storyListFragment = StoryListFragment.newInstance(StoriesBank.FEATURES);
            break;
        case(2):
             storyListFragment=  StoryListFragment.newInstance(StoriesBank.ARTS);
            break;
    }

    fragmentsArray[position] =  storyListFragment;
    return storyListFragment;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    StoryListFragment fragment = (StoryListFragment) super.instantiateItem(container, position);
    fragmentsArray[position] = fragment;
    return fragment;
}
Kamal
  • 3
  • 2
  • But this means that you hold all of the fragments, and I'm also not sure how well this will work when restoring the activity&fragment (which occur in case the app is in the background and the OS decides it needs some memory, so it closes some activities. – android developer Dec 25 '14 at 13:55
  • 1
    Yes. As i said, I used this system because I only had 5 lightweight fragments and I needed to keep track of them at all times. Also, if the developer is worried about fragment deletion, then he/she could make a conditional statement in the onCreate() of the activity as to re-instantiate the fragment that is currently visited (or all of them, depending on the specific situation). Again, this is by no means ideal for heavyweight fragments or an activity of many fragments. – Kamal Jan 04 '15 at 01:04
  • This is what I did too, but I hope it won't cause problems, as I've got myself into a little more complex situation: http://stackoverflow.com/q/27648565/878126 – android developer Jan 04 '15 at 10:14