John Petitto About Blog

Fragments, Don’t Hurt Me

Here’s a screen I was recently working on for an app at work:

It’s built with a Fragment containing a ViewPager. Each page in the ViewPager is served by a FragmentPagerAdapter. Pretty straight forward stuff.

On first launch it works as expected, but if the enclosing Activity is destroyed, no pages get recreated by the FragmentPagerAdapter.

Here’s the code for creating the ViewPager (shortened for brevity):

public class TPGTrackerFrag extends Fragment {
  @Override
  public View onCreateView(...) {
    ViewPager viewPager = (ViewPager) v.findViewById(R.id.tracker_view_pager);
    FragmentPagerAdapter adapter = new TrackerPagerAdapter(getFragmentManager());
    viewPager.setAdapter(adapter);

    TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.tracker_tabs);
    tabLayout.setupWithViewPager(viewPager);
  }
}

And the code for the TrackerPagerAdapter (also shortened for brevity):

private class TrackerPagerAdapter extends FragmentPagerAdapter {
  public TrackerPagerAdapter(FragmentManager fm) {
    super(fm);
  }

  @Override
  public Fragment getItem(int position) {
    // returns a Fragment for each tab
  }
}

Can you see what’s wrong?

It turns out the issue lies with the FragmentManager passed to the TrackerPagerAdapter. If we instead pass in the Fragment’s child FragmentManager, our code works as expected.

FragmentPagerAdapter adapter = new TrackerPagerAdapter(getChildFragmentManager());
viewPager.setAdapter(adapter);

A subtle but important difference. The FragmentManager returned by getFragmentManager() is bound to the enclosing Activity, while getChildFragmentManager() is tied to the Fragment itself. Since the Fragments loaded into the ViewPager are nested within our Fragment, we must use the child FragmentManager or we’ll see weird behavior like this.

We can forgo Fragments altogether by using a PagerAdapter to load views. This is how ViewPager was typically used before Fragments were introduced into the framework. If you’re looking to move away from Fragments entirely, I suggest checking out Conductor.

In our app we’ve hit similar snags with Fragments too. Issues around adding the same Fragment instance to a container, unexpected lifecycle events with the back stack when using add vs. replace, and a handful more have arisen (dive into this r/AndroidDev thread to read about many of them).

Generally speaking, it’s not very hard to get in trouble with the Fragment APIs. Their behavior is often unexpected and this tends to compound as the complexity of your app grows.