Using ViewPager
with FragmentPagerAdapter
or FragmentStatePagerAdapter
appears to be quite easy and enjoyable.
It could be done within few simple steps:
- Create layout .xml resource with
ViewPager
- Create adapter and implement all required methods
- Set adapter instance to your
ViewPager
instance.
Simple? Yes! – but like always only for easiest (also the most common) case
Problem occurs when we want to make something more sophisticated, i.e. add fragments dynamically – provide fragments/pages to adapter from its outside.
The Code
Take a look at quite popular AppIntro library. It gives user a possibility to simply create an app intro just by extending one AppIntro
Activitiy.
One extend, few Fragment slides added in init method, and we got it!
1 2 3 4 5 6 7 8 9 10 |
public class DisableSwipeIntro1 extends AppIntro { @Override public void init(Bundle savedInstanceState) { addSlide(SampleSlide.newInstance(R.layout.intro_disable)); addSlide(SampleSlide.newInstance(R.layout.intro2_disable)); addSlide(SampleSlide.newInstance(R.layout.intro3_disable)); } ... } |
So…the idea of this AppIntro
library is to separate and take off weight from a developer to manage all those adapters and viewpagers.
Look in the source code can bring us closer to a basic concept (as I could only imagine it) of this library
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
protected List<Fragment> fragments = new Vector<>(); @Override final protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.intro_layout); mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), fragments); pager = (AppIntroViewPager) findViewById(R.id.view_pager); pager.setAdapter(this.mPagerAdapter); // ... init(savedInstanceState); slidesNumber = fragments.size(); } public void addSlide(@NonNull Fragment fragment) { fragments.add(fragment); mPagerAdapter.notifyDataSetChanged(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class PagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragments; public PagerAdapter(FragmentManager fm, @NonNull List<Fragment> fragments) { super(fm); this.fragments = fragments; } @Override public Fragment getItem(int position) { return this.fragments.get(position); } @Override public int getCount() { return this.fragments.size(); } @NonNull public List<Fragment> getFragments() { return fragments; } } |
Every activity onCreate()
callback method calls init(savedInstanceState)
method to give a user possibility to add fragments/slies which later are used in FragmentPagerAdapter
class.
Caution.
So it looks like all good at the beginning, but yellow light may light up in the head of the developer.
Why?
Creating/providing new fragments instances every time in onCreate()
method.
Do you remember what we are doing when we want to create and just add single a fragment to our Activity
?
1 2 3 4 5 6 7 8 9 10 |
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Fragment newFragment = new OurSuperFragment(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.add(android.R.id.content, newFragment).commit(); } } |
We are checking if this is the first run or recreation after a configuration change.
After orientation change all fragments currently added to FragmentManager
are automatically restored and instantiated so there is no need to create them once again.
Same thing is with the ViewPager
and FragmentPagerAdapter
. Once the adapter adds a fragment to FragmentManager
after rotation it will not call getItem(int position)
adapter method, but it will look for an already recreated Fragment in FragmentManager
.
Line 11 – finding from manager, and only if the fragment is not yet there Line 16 gets new fragment instance from getItem()
method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Override public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } |
Conclusion
We should always remember about the automatic fragment restoration process done via FragmentManager
with companion of Activity
implementation.
Provide fragments only once at the beginning or only when they are needed (in case of an adapter and getItem(int position)
method call) not every time redundantly.