So in my MainActivity OnCreate I create 3 Fragments, each filled with different data. I then add these Fragments to my ViewPagerAdapter.
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
MealsFragment exploreFragment = null;
MealsFragment favoriteFragment = null;
MealsFragment localFragment = null;
//if there is no saved instance, create fragments with passed data as args and add them to the viewpageradapter
if(savedInstanceState == null){
exploreFragment = fetchExploreMealsDataAndCreateFragment();
favoriteFragment = fetchFavoriteMealsDataAndCreateFragment();
localFragment = fetchLocalMealsDataAndCreateFragment();
}
//add fragments to the adapter
viewPagerAdapter.addFragment(exploreFragment, "EXPLORE");
viewPagerAdapter.addFragment(favoriteFragment, "FAVORITES");
viewPagerAdapter.addFragment(localFragment, "LOCAL");
//set adapter to the viewpager and link it with the different tabs
mviewPager.setAdapter(viewPagerAdapter);
mtabLayout.setupWithViewPager(mviewPager);
The 3 different fetch methods get the data from an API or DB, so I don't want to call these methods every time I rotate my screen and go through my lifecycle. That's why I firstly check if the savedInstanceState is null. But what happens now is that if my savedInstanceState is not null, the Fragments will be null since i initialised them that way.
Apparently this is not a problem since when I rotate the screen, the fragments remain the same. I was wondering what is going on here behind the scenes as I don't think this is the correct way of handling my situation. Any suggestions of improvement are appreciated aswell.
Thanks in advance!
EDIT:
Forgot to mention that ViewPagerAdapter is my own implementation of FragmentPageAdapter
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
private final List<String> fragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
return fragmentList.get(i);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
public void addFragment(Fragment fragment, String title){
fragmentList.add(fragment);
fragmentTitleList.add(title);
}
}
I was wondering what is going on here behind the scenes
Alright, so, this is going to involve looking at the source for FragmentPagerAdapter
. What's relevant is the implementation of the instantiateItem()
method... though really just a portion of it.
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int 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));
}
[...]
}
Essentially, the getItem()
method from your ViewPagerAdapter
is only called once for each position for the lifetime of the Activity (even across configuration changes). Every time other than the first, the Fragment
object is retrieved directly from the FragmentManager
as opposed to the Adapter.
So yes, for all re-creations of your Activity, your Adapter is holding a List<Fragment>
that is just null
, null
, null
... but it doesn't matter, because this list is not accessed again.
HOWEVER
The above statements assume that every Fragment in your adapter was constructed and added to the FragmentManager
before your configuration change, and this is not necessarily guaranteed.
By default, the off-screen page limit for the ViewPager
is 1
. That means that your third page (your localFragment
), is not necessarily added to the ViewPager
, and therefore the FragmentManager
, on first launch. If you scroll over to the next page even one time, it will be, but this is not necessarily true.
Or perhaps you have manually set the off-screen page limit to be 2
, in which case all the pages/Fragments will be added immediately.
Probably the best thing to do is to change how you're using FragmentPagerAdapter
altogether. I'd put this inside your Activity
as an inner class:
private class ExampleAdapter extends FragmentPagerAdapter {
public ExampleAdapter() {
super(getSupportFragmentManager());
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0: return fetchExploreMealsDataAndCreateFragment();
case 1: return fetchFavoriteMealsDataAndCreateFragment();
case 2: return fetchLocalMealsDataAndCreateFragment();
default: throw new IllegalArgumentException("unexpected position: " + position);
}
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return "EXPLORE";
case 1: return "FAVORITES";
case 2: return "LOCAL";
default: throw new IllegalArgumentException("unexpected position: " + position);
}
}
@Override
public int getCount() {
return 3;
}
}
And then your onCreate()
could be changed to this:
FragmentPagerAdapter viewPagerAdapter = new ExampleAdapter();
mviewPager.setAdapter(viewPagerAdapter);
mtabLayout.setupWithViewPager(mviewPager);
The advantage of doing it this way is that you only call the "fetch and create" methods on demand, but still have the ability to call them after a configuration change in situations where they weren't loaded before the configuration change.