3

I am working on an app that has the following UI structure:

  • One Activity, called FoodActivity
    • This Activity implements bottom navigation. Hence, can show three categories of food: fruit, meat and fish
    • The Activity has a big Fragment container, where I attach the Fragment for fruit, meat and fish as the user interacts with the bottom tabs.
  • Each outer Fragment (fish, meat and fish) presents navigation between Fragments: it can show a list of fruits, and the the detail for the selected fruit.
    • Hence, the outer Fragments have another Fragment container in where I attach Fragments for the fruit list or fruit detail.

So, it's one main Activity, which has a big Fragment container where I swap Fragments, which in turn nest other Fragments

App UI structure

To switch from one outer Fragment to another using the tabs (ie: switch from fruit to meat), I perform a Fragment Transaction in the outer Fragment container:

private void switchFragment(Fragment fragment, String fragmentTag) {
    final FragmentManager fm = getSupportFragmentManager();
    final FragmentTransaction ft = fm.beginTransaction();

    ft.replace(R.id.fragment_outer, fragment, fragmentTag);
    ft.commit();
}

The problem is that when switching the first-level Fragments, the state of their ChildFragmentManager is not kept.

For example, imagine that: - I start in FuitFragment - The FruitFragment, when created, attaches the nested FruitListFragment - The user navigates in the nested Fragment container, from FruitListFragment to FruitDetailFragment - The user switches to another tab - The user switches back to the fruit tab - The child `FragmentManager* of the FuitFragment does not automatically put FruitDetailFragment in the nested fragment container.

More specifically, when switching back to the outer Fragment:

  • onCreate is called, with savedInstance == null
  • onCreateView is called, with savedInstance == null

Hence, as the outer Fragment onCreate method is called, and I cannot tell whether or not to attach the child Fragment (as one usually does with Activities). Moreover if I don't attach it (using an instance variable to check if I'm switching back to it) the child Fragment container will be just empty.

Experiments and notes

  • I have experimented that if I add the outer fragment transaction to the backstack when attaching it, when switching back to it, onCreate method is NOT called and the child FragmentManager will remember and attach the fragment it had before. However, I cannot add it to the backstack as per Google's specifications on bottom navigation.

  • Setting setRetainInstace to true does not make any effect.

So, what should I do for properly restoring the state of the child FragmentManager?

Am I doing something wrong, or is it that everything around nested Fragments in Android is not well supported (or a bit broken) and I simply should not provide navigation using nested Fragments?

GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61
  • A simple solution would be to replace `replace()` method call with `add()` `attach()` and `remove()` and `detach` depending on your need. `detach()` method call retains the state of the fragment and only hides from the UI. – Abbas Jul 27 '16 at 11:16
  • That's right Abbas. I came into that conclusion when reading the code of FragmentPagerAdapter. I have posted the answer with the code that works, but if you want post your answer and I'll mark it as the valid one. – GaRRaPeTa Jul 27 '16 at 11:47

2 Answers2

1

As Abbas pointed out, the problem was that I was using replace to switch between fragments.

I have changed to code in the Activity that puts the outer Fragment, and it works:

private void showChildFragment(int itemId) {
    final FragmentManager fragmentManager = getSupportFragmentManager();
    final FragmentTransaction  transaction = fragmentManager.beginTransaction();

    final Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_outer);
    if (currentFragment != null) {
        Log.v(TAG, "Detaching item #" + currentFragment);
        currentFragment.setMenuVisibility(false);
        currentFragment.setUserVisibleHint(false);
        transaction.detach(currentFragment);
    }

    // Do we already have this fragment?
    final String tag = makeFragmentTag(container.getId(), itemId);
    Fragment fragment = fragmentManager.findFragmentByTag(tag);
    if (fragment == null) {
        fragment = createFragmentForViewId(itemId);
        Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        transaction.add(container.getId(), fragment, tag);
    } else {
        Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        transaction.attach(fragment);
    }
    fragment.setMenuVisibility(true);
    fragment.setUserVisibleHint(true);

    transaction.commitAllowingStateLoss();
    fragmentManager.executePendingTransactions();
}

private Fragment createFragmentForViewId(int itemId) {
    switch (itemId) {
        case FRAGMENT_ID_LIBRARY:
            return LibraryNavigationFragment.createInstance();
        case FRAGMENT_ID_FEED:
            return WebAppFragment.createInstance("feed");
        case FRAGMENT_ID_SUGGEST:
            return WebAppFragment.createInstance("suggest");
        default:
            throw new IllegalArgumentException();

    }
}

This code is almost copy pasted from android.support.v4.app.FragmentPagerAdapter as ViewPagers using Fragments work like I wanted to.

GaRRaPeTa
  • 5,459
  • 4
  • 37
  • 61
-1

With getChildFragmentManager() it won't crash.

private void switchFragment(Fragment fragment, String fragmentTag) {
    final FragmentManager fm = getChildFragmentManager();
    final FragmentTransaction ft = fm.beginTransaction();

    ft.replace(R.id.fragment_outer, fragment, fragmentTag);
    ft.commit();
}
Ilia Grabko
  • 1,119
  • 1
  • 8
  • 13