FragmentPagerAdapter and FragmentStatePagerAdapter

bpawlowski picture bpawlowski · May 17, 2013 · Viewed 9.9k times · Source

I am currently thinking which implementation of PagerAdapter should I use. I've got dilemmas connected to both of them. Let me show you what are these.

1# FragmentPagerAdapter

Works fine, It creates new instances of fragments when none previous instances are available and retrieves previously fragments when there's such opportunity.

I've read recently on StackOverflow that, method PagerAdapter's getItem() method is called ONLY when it need to create fragment, but - it is called over and over and I had to handle creating new instances and retrieving old ones inside this method's body.

BUT - Only few callback and life-cycle methods are called. For example I can't manage to onSaveInstanceState to be called. So there's no way to save fragment's state - of course I can use SharedPreferences or something else, but I wanted to use callback methods. Is there any way to accomplish that?

2# FragmentStatePagerAdapter

Works perfectly, saves state of every fragment that ViewPager holds.

BUT - this PagerAdapter ALWAYS creates new fragment. I checked it inside constructor.

Isn't that inefficient? I saw Romain Guy on Google I/O materials saying that creating new Views is not efficient, especially when we creating lots of Views like in ListView so we use convertView to retrieve existing View and change it for our purposes as many times as we want to. So fliping between pages is quite similar - many new Views - because fragment is a some kind of View

In both PagerAdapters I tried trick with Overriding destroyItem() method but It didn't work at all.

And here's my question.

What should I do?

Should I use SharedPreferences and option #1 with FragmentPagerAdapter or option #2 with FragmentStatePagerAdapter?

Is there any possibility, that I do something wrong within these adapters, to they behave not as we expect to?

Below is my code divided into "readable" parts

PagerAdapter part #1:

/**
 * Adapter class to {@link WizardPager}
 */
public static class WizardCrazyAdapter extends FragmentStatePagerAdapter
    implements OnPageChangeListener{

    public static final String tag = "android:switcher:"+R.id.pager_w+":";

    /**
     * Refernece to root activity
     */
    WizardActivity wizardActivity;

    /**
     * list of fragments
     */
    private final ArrayList<FragmentInfo> fInfos = new ArrayList<FragmentInfo>();

    private short prevPageNumber = 0;

    /**
     * Constructor of adapter
     * @param wizardActivity
     *            {@link WizardActivity} as reference to activity root
     */
    public WizardCrazyAdapter(WizardActivity wizardActivity) {
        super(wizardActivity.getSupportFragmentManager());
        this.wizardActivity = wizardActivity;           
    }

    static final class FragmentInfo {
        private final Class<?> _clss;
        private Bundle _args;

        public FragmentInfo(Class<?> clss, Bundle args) {
            _clss   =clss;
            _args   =args;
        }
    }

    public void addPage(Class<?> clss, Bundle args){
        FragmentInfo fi = new FragmentInfo(clss, args);         
        fInfos.add(fi);
    }

    /**
     * Return number of pages
     */
    public int getCount() {
        return fInfos.size();
    }

PagerAdapter part #2:

    /**
     * Searches in {@link FragmentManager} for {@link Fragment} at specified position
     * @param position
     * @return
     */
    private AbstractWizardFragment getFragmentAt(int position){
        FragmentManager fm = wizardActivity.getSupportFragmentManager();

        AbstractWizardFragment awf = (AbstractWizardFragment) fm.findFragmentByTag(tag+position);

        return awf;         
    }

    /**
     * Return page of view pager
     */
    @Override
    public Fragment getItem(int position) {
        /*finding existing instance of fragment*/
        AbstractWizardFragment awf = getFragmentAt(position);

        if(awf == null){
            /*creating new instance if no instance exist*/
            Log.v("WizardActivity", "creating new Fragment");
            FragmentInfo fi = fInfos.get(position);
            awf = (AbstractWizardFragment) Fragment.instantiate(wizardActivity, fi._clss.getName());
        }else{
            Log.v("WizardActivity", "found existing Fragment");
        }

        return awf;
    }

PagerAdapter part #3:

    @Override
    public void onPageSelected(int pageNumber) {
        wizardActivity.stepFragment.setCurrentStepAndChangeText(pageNumber);

        if(pageNumber != prevPageNumber){
            AbstractWizardFragment prevFragment = (AbstractWizardFragment) getItem(prevPageNumber);//TODO change if any problems
            prevFragment.onDetachedFromViewPager(wizardActivity.mForm);
        }

        AbstractWizardFragment currFragment = (AbstractWizardFragment) getItem(pageNumber);//TODO change if any problems
        currFragment.onAttachedToViewPager(wizardActivity.mForm);

        prevPageNumber = (short) pageNumber;


        Log.d("WizardActivity", "onPageSelected");
    }

    @Override
    public Object instantiateItem(ViewGroup arg0, int arg1) {
        Log.d("WizardActivity", "instantiateItem "+arg1);
        return super.instantiateItem(arg0, arg1);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
   //           super.destroyItem(container, position, object);

        Log.v("WizardActivity", "that would be destroy");
    }
}

ViewPager code:

public class WizardPager extends ViewPager{

/**
 * Flag to check if view pager must be scrolled 
 */
protected boolean isScrollable;

/**
 * Default constructor
 * @param context {@link Context}
 */
public WizardPager(Context context) {
    super(context);
    isScrollable = true;
}

/**
 * Standard constructor
 * @param context {@link Context}
 * @param attrs {@link AttributeSet}
 */
public WizardPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    isScrollable = true;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (this.isScrollable) {
        return super.onTouchEvent(event);
    }

    return false;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (this.isScrollable) {
        return super.onInterceptTouchEvent(event);
    }

    return false;
}

/**
 * Enable scroll of pages
 */
public void enableScroll(){
    this.isScrollable = true;
}

/**
 * Disable scroll of pages
 */
public void disableScroll(){
    this.isScrollable = false;
}

/**
 * Check if pages can be scrolled
 * @return
 */
public boolean isScrollable(){
    return isScrollable;
}
}

Answer

bpawlowski picture bpawlowski · May 17, 2013

I messed up in onPageSelectedMethod - I was forcing FragmentStatePagerAdapter to call getItem - which instantiates Fragment at least once - method whenever page is changed. That's why I complained about instantiating Fragment every time page is changed :)

Instead of getItem() I should call there my getFragmentAt() method, and the whole callback should look like that.

@Override
public void onPageSelected(int pageNumber) {
    wizardActivity.stepFragment.setCurrentStepAndChangeText(pageNumber);

    if(pageNumber != prevPageNumber){
        AbstractWizardFragment prevFragment = (AbstractWizardFragment) getFragmentAt(prevPageNumber);
        prevFragment.onDetachedFromViewPager(wizardActivity.mForm);
    }

    AbstractWizardFragment currFragment = (AbstractWizardFragment) getFragmentAt(pageNumber);
    currFragment.onAttachedToViewPager(wizardActivity.mForm);

    prevPageNumber = (short) pageNumber;


    Log.d("WizardActivity", "onPageSelected");
}

Although It works fine. There still might be risk, that Fragment won't be found and the method will return null - and then consequently NPE will be thrown.