The tale of the expiring fragment in a FragmentPagerAdapter

I was testing my tablet layout of my app and noticed a really strange thing: my detail fragment (of a parent and detail fragment set) when a certain combination of various device rotation and menu selection was loosing my 1st tab of my FragmentPagerAdapter.

What was going on here, and more importantly, how could I fix it?

I’m not sure where I found my original example to create the code. Here is a cut down version of my Tab Page Adapter :


public class AdapterTabsPager extends FragmentPagerAdapter {
private final static String TAG = AdapterTabsPager.class.getSimpleName();

private final String [] tabTitles = {"title 1", "title 2", "title 3", "title 4", "title 5", "title 6"};

private FragmentOne mFragment1 = null;
private FragmentTwo mFragment2 = null;
private FragmentThree mFragment3 = null;
private FragmentFour mFragment4 = null;
private FragmentFive mFragment5 = null;
private FragmentSix mFragment6 = null;

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

@Override
public Fragment getItem(int position) {

switch (position) {
case 0:
if (mFragment1 == null || !(mFragment1 instanceof FragmentMovieDetailDetails)) {
mFragment1 = FragmentOne.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment1.refreshTab");
mFragment1.refreshTab();
}
return mFragment1;

case 1:
if (mFragment2 == null || !(mFragment2 instanceof FragmentTwo)) {
mFragment2 = FragmentTwo.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment2.refreshTab");
mFragment2.refreshTab();
}
return mFragment2;

case 2:
if (mFragment6 == null || !(mFragment6 instanceof FragmentSix)) {
mFragment6 = FragmentSix.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment6.refreshTab");
mFragment6.refreshTab();
}
return mFragment6;

case 3:
if (mFragment5 == null || !(mFragment5 instanceof FragmentFive)) {
mFragment5 = FragmentFive.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment5.refreshTab");
mFragment5.refreshTab();
}
return mFragment5;

case 4:
if (mFragment4 == null || !(mFragment4 instanceof FragmentFour)) {
mFragment4 = FragmentFour.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment4.refreshTab");
mFragment4.refreshTab();
}
return mFragment4;

case 5:
if (mFragment3 == null || !(mFragment3 instanceof FragmentThree)) {
mFragment3 = FragmentThree.newInstance();
} else {
// TODO reset data here
Log.d(TAG, "mFragment3.refreshTab");
mFragment3.refreshTab();
}
return mFragment3;
}

// getItem is called to instantiate the fragment for the given page.
throw new RuntimeException("Position " + position +
" is greater than the number of tabs: " + tabTitles.length);
}

@Override
public int getCount() {
return tabTitles.length;
}

// This sets the top tab name
@Override
public CharSequence getPageTitle(int position) {
return tabTitles[position];
}

/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/* Start of fetch detail methods section */
/******************************************************************************************/

public void refreshTabs() {
//        Log.d(TAG, "refreshTabs()");
if (mFragment1 != null) {
mFragment1.resetDetails();
mFragment1.refreshTab();
}
if (mFragment2 != null) {
mFragment2.resetDetails();
mFragment2.refreshTab();
}
if (mFragment6 != null) {
mFragment6.resetDetails();
mFragment6.refreshTab();
}
if (mFragment5 != null) {
mFragment5.resetDetails();
mFragment5.refreshTab();
}
if (mFragment4 != null) {
mFragment4.resetDetails();
mFragment4.refreshTab();
}
if (mFragment3 != null) {
mFragment3.resetDetails();
mFragment3.refreshTab();
}
}
}

What I needed the tab adapter to do was keep track of the fragments and serve up the appropriate fragment for the given tab as well as allow one function call to go into each fragment’s methods to update (or recreate) the data to show to the user.

Given a lot of Log.d and debugging, I found out that my mFragment1 somehow was null: garbage collected most likely.

I went out searching for the solution. While not at the top, this SO post pretty much nailed it: https://stackoverflow.com/questions/14035090/how-to-get-existing-fragments-when-using-fragmentpageradapter

I shouldn’t have been trying to save the fragments in the getItem method, but done it in a instantiateItem method instead – which will get called when re-instantiating GC items. I came up with this replacement:


public class AdapterTabsPager extends FragmentPagerAdapter {
private final static String TAG = AdapterTabsPager.class.getSimpleName();

private final String [] tabTitles = {"title 1", "title 2", "title 3", "title 4", "title 5", "title 6"};

private FragmentOne mFragment1 = null;
private FragmentTwo mFragment2 = null;
private FragmentThree mFragment3 = null;
private FragmentFour mFragment4 = null;
private FragmentFive mFragment5 = null;
private FragmentSix mFragment6 = null;

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

/***
* Do NOT try to save references to the Fragments in getItem(),
* because getItem() is not always called. If the Fragment
* was already created then it will be retrieved from the FragmentManger
* and not here (i.e. getItem() won't be called again).
* @param position tab position
* @return Fragment
*/
@Override
public Fragment getItem(int position) {

switch (position) {
case 0:
return FragmentOne.newInstance();

case 1:
return FragmentTwo.newInstance();

case 2:
return FragmentSix.newInstance();

case 3:
return FragmentFive.newInstance();

case 4:
return FragmentFour.newInstance();

case 5:
return FragmentThree.newInstance();

default:
return null;
}
}

/***
* Here we can finally safely save a reference to the created
* Fragment, no matter where it came from (either getItem() or
* FragmentManger). Simply save the returned Fragment from
* super.instantiateItem() into an appropriate reference depending
* on the ViewPager position.
* @param container
* @param position tab position
* @return Fragment
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);

switch (position) {
case 0:
mMovieDetails = (FragmentOne) createdFragment;
break;
case 1:
mMovieRelease = (FragmentTwo) createdFragment;
break;
case 2:
mMovieCast = (FragmentSix) createdFragment;
break;
case 3:
mMoviesCrew = (FragmentFive) createdFragment;
break;
case 4:
mMovieVideos = (FragmentFour) createdFragment;
break;
case 5:
mMovieReviews = (FragmentThree) createdFragment;
break;
}

return createdFragment;
}

@Override
public int getCount() {
return tabTitles.length;
}

// This sets the top tab name
@Override
public CharSequence getPageTitle(int position) {
return tabTitles[position];
}

/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/* Start of fetch detail methods section */
/******************************************************************************************/

public void refreshTabs() {
if (mFragment1 != null) {
mFragment1.resetDetails();
mFragment1.refreshTab();
}
if (mFragment2 != null) {
mFragment2.resetDetails();
mFragment2.refreshTab();
}
if (mFragment6 != null) {
mFragment6.resetDetails();
mFragment6.refreshTab();
}
if (mFragment5 != null) {
mFragment5.resetDetails();
mFragment5.refreshTab();
}
if (mFragment4 != null) {
mFragment4.resetDetails();
mFragment4.refreshTab();
}
if (mFragment3 != null) {
mFragment3.resetDetails();
mFragment3.refreshTab();
}
}
}

Testing again found that it worked correctly.

Advertisements