Michael
Michael

Reputation: 4000

How to restore child fragments in TabHost/ViewPager parent fragment

The problem is highlighted below, the first part describes what is working so far.

I use multiple fragments with one activity. The upper part of the figure shows a single fragment, e.g. a list with some random content. If I click somewhere it will open the AboutTheAppFragment. When I click back it shows ASingleFragment again. This works perfectly, but that was the easy part :)

a busy cat

Here is the code snipped I used to open the new fragment.

AboutTheAppFragment aboutTheAppFragment = new AboutTheAppFragment();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(mContainer.getId(), aboutTheAppFragment, AboutTheAppFragment.class.getName());
fragmentTransaction.addToBackStack(null);

Now here's my problem

Instead of ASingleFragment I like to display a TabHostPagerFragment which uses a TabHost and a ViewPager to display the fragments (see figure below). Lets say I have the two fragments TabOneFragment and TabTwoFragment, but only one is shown in the ViewPager. This works great too, I can simply slide on the screen and it switches between the two tab fragments. However, if I click somewhere to open the AboutTheAppFragment and click back again, both fragments TabOneFragment and TabTwoFragment do not display anything. If I rotate the device it reloads/restores the fragments again and everything works fine. So my question is, how can I add TabOneFragment and TabTwoFragment to the backstack, that they are shown correctly when I click the back button? Note that it works like in scenario 1 when I set TabOneFragment as ASingleFragment, then I click somewhere and click back again. But it does not work when I use the TabHostPagerFragment.

a busy cat

Here are some code snippets of the TabHostPagerFragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // Inflate the tabs pager fragment in the container
    View view = inflater.inflate(R.layout.fragment_tabs_pager, container, false);
    mContainer = container;

    // Set up the TabHost
    mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
    mTabHost.setup();

    // Set up the ViewPager
    mViewPager = (ViewPager) view.findViewById(R.id.pager);

    return view;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // Now initialize the TabsAdapter which will be used to manage the tabs
    mTabsAdapter = new TabsAdapter(activity, mTabHost, mViewPager);

    // Finally add the tabs to the TabsAdapter: meal plan for today and meal plan for the week 
    mTabsAdapter.addTab(mTabHost.newTabSpec("TabOne").setIndicator("TabOne", TabOneFragment.class, null);
    mTabsAdapter.addTab(mTabHost.newTabSpec("TabTwo").setIndicator("TabTwo", TabTwoFragment.class, null);

    if (savedInstanceState != null) {
        // NEVER REACHES THIS CASE
        mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
    }
}

I noticed that savedInstanceState is always null. Any idea how I could fix this and store the tabs in a saved instance state?

I'ld appreciate any help.

Best regards, Michael

EDIT :

Here is the calling hierarchy of the fragments:

09-25 09:19:25.695: V/TabsPagerParentFragment(1820): onPause
09-25 09:19:25.695: V/TabsPagerParentFragment(1820): onStop
09-25 09:19:25.703: V/TabsPagerParentFragment(1820): onDestroyView
09-25 09:19:25.710: V/AboutTheAppFragment(1820): onAttach
09-25 09:19:25.710: V/AboutTheAppFragment(1820): onCreate: savedInstanceState == null
09-25 09:19:25.710: V/AboutTheAppFragment(1820): onCreateView: savedInstanceState == null
09-25 09:19:25.812: V/AboutTheAppFragment(1820): onViewCreated
09-25 09:19:25.812: V/AboutTheAppFragment(1820): onActivityCreated
09-25 09:19:25.812: V/AboutTheAppFragment(1820): onViewStateRestored
09-25 09:19:25.812: V/AboutTheAppFragment(1820): onStart
09-25 09:19:25.812: V/AboutTheAppFragment(1820): onResume
09-25 09:19:27.304: V/AboutTheAppFragment(1820): onPause
09-25 09:19:27.304: V/AboutTheAppFragment(1820): onStop
09-25 09:19:27.304: V/AboutTheAppFragment(1820): onDestroyView
09-25 09:19:27.304: V/AboutTheAppFragment(1820): onDestroy
09-25 09:19:27.312: V/TabsPagerParentFragment(1820): onCreateView: savedInstanceState == null
09-25 09:19:27.320: V/TabsPagerParentFragment(1820): onViewCreated
09-25 09:19:27.320: V/TabsPagerParentFragment(1820): onActivityCreated
09-25 09:19:27.328: V/TabsPagerParentFragment(1820): onViewStateRestored
09-25 09:19:27.328: V/TabsPagerParentFragment(1820): onStart
09-25 09:19:27.328: V/TabsPagerParentFragment(1820): onResume

Upvotes: 3

Views: 3597

Answers (2)

Len
Len

Reputation: 542

I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.

In the fragment that holds your Tabs:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_tab, container, false);
    tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
    viewPager = (ViewPager) view.findViewById(R.id.viewPager);

    //Important!!! Do not fire the existing adapter!!
    if (viewPagerAdapter == null) {
        viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
        viewPagerAdapter.addFragments(new AFragment(), "A");
        viewPagerAdapter.addFragments(new BFragment(), "B");
        viewPagerAdapter.addFragments(new CFragment(), "C");
    }
    //Allocate retention buffers for three tabs, mandatory
    viewPager.setOffscreenPageLimit(3);
    tabLayout.setupWithViewPager(viewPager);
    viewPager.setAdapter(viewPagerAdapter);

    return view;
}

Upvotes: 0

NeilS
NeilS

Reputation: 635

In your TabHostFragment, TabOneFragment and TabTwoFragment's constructors call:

   setRetainInstance(true);

You currently have nothing that is 'reconstructing' the fragments' state. When you replace ASingleFragment with AboutTheAppFragment ASingleFragment is destroyed and forgotten until you press 'back', at which point the system will recreate the view heirachy but none of their contents. Calling setRetainInstance(true) will tell the system to retain the entire fragment including its data so it can be restored.

onActivityCreated()'s saveInstanceState doesn't come into play here, that's for the parent Activity not the child Fragments.

Upvotes: 1

Related Questions