Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Child fragment gets destroyed for no good reason

Info: I have a 2 pane layout (2 child Fragments) inside a ParentFragment, which, of course, is inside a FragmentActivity. I have setRetainInstance(true) on the ParentFragment. On orientation change, the left child fragment doesn't get destroyed (onCreate() doesn't get called), which is normal (because of the parent retaining its instance).

Problem: On orientation change, the right fragment gets destroyed (onCreate() gets called). Why the hell is the right fragment destroyed and the left one isn't ?

EDIT: If I remove setRetainInstance(true), then the left fragment's onCreate() gets called twice (lol wtf) and the right fragment's onCreate() gets called once. So this isn't good either...

Code below for the ParentFragment:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    this.setRetainInstance(true);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.fragment_schedule, container, false);
    setHasOptionsMenu(true);


    if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) == null || 
            !getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout())
    {
        if (mPresentationsListFragment == null)
            mPresentationsListFragment = PresentationsListFragment.newInstance(PresentationsListFragment.TYPE_SCHEDULE, mScheduleDate);
        getChildFragmentManager().beginTransaction()
                                     .replace(R.id.fragment_schedule_framelayout_left, mPresentationsListFragment)
                                     .commit();
    }
    mPresentationsListFragment.setOnPresentationClickListener(this);


    return view;
}


@Override
    public void onPresentationClick(int id)
    {
        if (Application.isDeviceTablet(getActivity()))
        {
            if (getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_right) == null)
            {
                if (mPresentationDetailFragment == null)
                    mPresentationDetailFragment = PresentationDetailFragment.newInstance(id);
                else
                    mPresentationDetailFragment.loadPresentation(id);
                getChildFragmentManager().beginTransaction()
                                           .replace(R.id.fragment_schedule_framelayout_right, mPresentationDetailFragment)
                                           .commit();
            }
            else
                mPresentationDetailFragment.loadPresentation(id);
        }
        else
        {
            Intent presentationDetailIntent = new Intent(getActivity(), PresentationDetailActivity.class);
            presentationDetailIntent.putExtra(PresentationDetailActivity.KEY_PRESENTATION_ID, id);
            startActivity(presentationDetailIntent);
        }
    }

LE Solution: Thanks a lot to antonyt , the answer is below. The only changes needed to pe performed reside inside onCreateView() of the parent Fragment.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.fragment_schedule, container, false);
    setHasOptionsMenu(true);


    if (getChildFragmentManager().findFragmentById(R.id.fragment_presentations_framelayout_left) == null)
    {
        mPresentationsListFragment = PresentationsListFragment.newInstance();
        mPresentationsListFragment.setOnPresentationClickListener(this);
        getChildFragmentManager().beginTransaction()
                .add(R.id.fragment_presentations_framelayout_left, mPresentationsListFragment)
                .commit();
    }


    return view;
}
like image 631
Bogdan Zurac Avatar asked Mar 04 '13 14:03

Bogdan Zurac


1 Answers

From what I understand, if you have setRetainInstance(true) on the parent fragment with the above code, your left fragment should be recreated but your right fragment should not be, when changing orientation. This is backwards to what you wrote above, but I will explain why this is the case anyway. If you have setRetainInstance(false) on the parent fragment, you indeed should see the left fragment being created twice and the right fragment being created once.

Case 1: setRetainInstance(true)

Your parent fragment will not be destroyed on rotation. However, it will still recreate its views each time (onDestroyView and onCreateView will be called, in that order). In onCreateView you have code to add your left fragment under certain conditions. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left) should be non-null, since a fragment was added to that container previously. getChildFragmentManager().findFragmentById(R.id.fragment_schedule_framelayout_left).isInLayout() should be false since only fragments added via XML will cause it to return true. The overall condition is true and so a new instance of your left fragment will be created and it will replace the old one. Your right fragment is only instantiated during a click event and so no special behavior happens.

Summary: Parent fragment remains, new left fragment is created, right fragment remains.

Case 2: setRetainInstance(false)

Your parent fragment is destroyed, and so are the left and right fragments. All three fragments are recreated automatically by Android. Your parent fragment will then get a chance to create its view, and it will create a new instance of the left fragment as per the explanation above. The just-created left fragment will be replaced by this new instance. You will observe that a left fragment will be destroyed and another left fragment will be created. No special behavior happens for the right fragment.

Summary: New parent fragment is created, two new left fragments are created, new right fragment is created.

If you are sure that in the setRetainInstance(true) case, your right fragment is being destroyed and not your left one, please post a sample project to github/etc. that demonstrates this.

Update: Why the right fragment gets removed if you use FragmentTransaction.replace() on the left fragment

Because of the inner conditional, your code will try to replace your left fragment with itself on the same container.

Here is the code snippet from the Android 4.1 source code that handles a replace:

...
case OP_REPLACE: {
    Fragment f = op.fragment;
    if (mManager.mAdded != null) {
        for (int i=0; i<mManager.mAdded.size(); i++) {
            Fragment old = mManager.mAdded.get(i);
            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
                    "OP_REPLACE: adding=" + f + " old=" + old);
            if (f == null || old.mContainerId == f.mContainerId) {
                if (old == f) {
                    op.fragment = f = null;
                } else {
                    if (op.removed == null) {
                        op.removed = new ArrayList<Fragment>();
                    }
                    op.removed.add(old);
                    old.mNextAnim = op.exitAnim;
                    if (mAddToBackStack) {
                        old.mBackStackNesting += 1;
                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
                                + old + " to " + old.mBackStackNesting);
                    }
                    mManager.removeFragment(old, mTransition, mTransitionStyle);
                }
            }
        }
    }
    if (f != null) {
        f.mNextAnim = op.enterAnim;
        mManager.addFragment(f, false);
    }
} break;
...

If you try to replace the same fragment with itself, there is some code to try and ignore this operation:

if (old == f) {
    op.fragment = f = null;
}

Since f is null, and we are still continuing to iterate through our fragments, this seems to have the side effect of removing every subsequent fragment from the FragmentManager. I don't think this is intentional, but at the very least explains why your right fragment is getting destroyed. Not using replace / not replacing the same fragment with itself can fix your issues.

Interestingly, this was a recent change and did not exist in previous versions of Android. https://github.com/android/platform_frameworks_support/commit/5506618c80a292ac275d8b0c1046b446c7f58836

Bug report: https://code.google.com/p/android/issues/detail?id=43265

like image 170
antonyt Avatar answered Sep 30 '22 07:09

antonyt