Search code examples
androidandroid-fragmentsrotationandroid-nested-fragment

Nested fragments survive screen rotation


I've faced with an issue with Nested Fragments in Android. When I rotate the screen the Nested Fragments survive somehow. I've come up with a sample example to illustrate this issue.

public class ParentFragment extends BaseFragment
{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return  inflater.inflate(R.layout.fragment_parent, container);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

         getChildFragmentManager()
                 .beginTransaction()
                 .add(getId(), new ParentFragmentChild(), ParentFragmentChild.class.getName())
                 .commit();
    }

    @Override
    public void onResume() {
        super.onResume();
        log.verbose("onResume(), numChildFragments: " + getChildFragmentManager().getFragments().size());
    }     
}

public class ParentFragmentChild extends BaseFragment
{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_child, null);
    }
}

BaseFragment just logs method calls. This is what I see when I rotate the screen.

When Activity initially appears

ParentFragment﹕ onAttach(): ParentFragment{420d0a98 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420d08d0 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 1
ParentFragmentChild﹕ onResume()

Screen rotation #1

ParentFragmentChild﹕ onPause()
ParentFragment﹕ onPause()
ParentFragment﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onStop()
ParentFragment﹕ onStop()
ParentFragmentChild﹕ onDestroyView()
ParentFragment﹕ onDestroyView()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragment﹕ onDestroy()
ParentFragment﹕ onDetach()
ParentFragment﹕ onAttach(): ParentFragment{4211bc38 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420f4180 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{42132a08 #1 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 2
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()

Screen rotation #2

ParentFragmentChild﹕ onPause()
ParentFragmentChild﹕ onPause()
ParentFragment﹕ onPause()
ParentFragment﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onSaveInstanceState()
ParentFragmentChild﹕ onStop()
ParentFragmentChild﹕ onStop()
ParentFragment﹕ onStop()
ParentFragmentChild﹕ onDestroyView()
ParentFragmentChild﹕ onDestroyView()
ParentFragment﹕ onDestroyView()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragmentChild﹕ onDestroy()
ParentFragmentChild﹕ onDetach()
ParentFragment﹕ onDestroy()
ParentFragment﹕ onDetach()
ParentFragment﹕ onAttach(): ParentFragment{42122a48 #0 id=0x7f060064}
ParentFragment﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420ffd48 #0 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{420fffa0 #1 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragment﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onViewCreated()
ParentFragmentChild﹕ onAttach(): ParentFragmentChild{42101488 #2 id=0x7f060064 com.kinoteatr.ua.filmgoer.test.ParentFragmentChild}
ParentFragmentChild﹕ onCreate()
ParentFragmentChild﹕ onViewCreated()
ParentFragment﹕ onResume()
ParentFragment﹕ onResume(), numChildFragments: 3
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()
ParentFragmentChild﹕ onResume()

They keep getting multiplied. Does anybody know why is that ?


Solution

  • When I rotate the screen the Nested Fragments survive somehow.

    They survive for the same reason the ParentFragment survives even when you are not retaining the instance with setRetainInstance(). The FragmentManager is that reason, in this case the ChildFragmentManager the ParentFragment uses to handle the nested fragments.

    Some things you need to know:

    • The FragmentManager is responsible for managing the fragments and adding them to the activity's view hierarchy.
    • The FragmentManager handles two things:
      • A list of fragments (remember this!).
      • back stack of fragment transactions.
    • To add, remove, attach, detach or replace fragments in the fragment list you use Fragment Transactions.
    • When you ask the FragmentManager for a fragment using the container view id (findFragmentById), if the fragment is already in the list, the FragmentManager will return it. Then you can use it.

    When Activity initially appears

    Since the app just started, you only have one instance of each fragment. One of ParentFragment and one of ParentFragmentChild. So far so good.

    Screen rotation #1

    At this point, the Activity's FragmentManager and the ParentFragment's ChildFragmentManagersave its list of fragments. Then, you can see how both fragments are completely destroyed.

    When the Activity is recreated, the new FragmentManagers retrieve the list and recreate the listed fragments to make everything as it was before the orientation change. Note that are not the same instances, these are new fragments recreated by Android (that's why you can't have a Fragment without the empty constructor, Android needs it to recreate the fragments).

    Now, this is the code inside your ParentFragment:

    getChildFragmentManager()
                 .beginTransaction()
                 .add(getId(), new ParentFragmentChild(), ParentFragmentChild.class.getName())
                 .commit();
    

    You are not trying to find out if ChildFragmentManager already has a ParentFragmentChild in its list (which already has). That's the reason you can see two fragments being created, the first one is being recreated by the ChildFragmentManager, the second is created by the previous code.

    Screen rotation #2

    There were two ParentChildFragment in the ChildFragmentManager's list before the second orientation change, they were recreated and one more is created by the code.

    It's better to use the fragments the FragmentManager recreates instead of creating new ones.