Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception when trying to save state of nested fragment [Fragment no longer exists for key android:target_state]

In order to focus on the problem I will simplify the case to the following - I have an activity A and a fragment F which is adding another fragment Child. The simplified code of each is

Activity A

@Override
protected void onCreate(Bundle savedInstanceState) {
    // do some stuff
    FragmentManager fm = getSupportFragmentManager();
    F f = new F();
    fm.beginTransaction()
            .add(R.id.content, f)
            .commit();
}

Fragment F

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // do some stuff
    FragmentManager fm = getChildFragmentManager();
    FragmentTransaction transaction = fm.beginTransaction();
    ChildFragment childFragment = new ChildFragment();
    childFragment.setTargetFragment(this, 1);
    transaction.add(R.id.f, childFragment);
    transaction.commit();

    return view;
}

The code of the child fragment is not relevant to the problem so I wont post it.

Using this code everything seemed to work properly until I integrated Firebase and start getting the following crash report

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.test/com.test.test.A}: java.lang.IllegalStateException: Fragment no longer exists for key android:target_state: index 1
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2377)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)
    at android.app.ActivityThread.access$800(ActivityThread.java:151)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342)
    at android.os.Handler.dispatchMessage(Handler.java:110)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:5341)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:825)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:641)
    at dalvik.system.NativeStart.main(Native Method)

At first I couldn't reproduce the exception but after testing for a while I found that if the developer option Do Not Keep Activities is on it happens almost every time I bring the activity to background and resume it. I think in the normal case it will happen when the activity is put in background and the app get destroyed.

After doing some research I come to the conclusion that the actual reason for the crash is that fragment F is set as target fragment for it's child. I can confirm that if i don't set the target fragment the crash do not occur.

I'm not absolute sure but it seems that the reason for the crash is that the Child Fragment and its target fragment are in different FragmentManagers. So the first thing I tried was to put all fragments in the activity's fragment manager.

Fragment F

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // do some stuff
    // I do not want to use private fragment manager but rather use the activity's
    // FragmentManager fm = getChildFragmentManager();
    FragmentManager fm = getFragmentManager();
    // do the other stuff
}

This solved the problem. But lead to another one. When i remove the fragment from the activity (i want to replace it with another fragment). The child fragment failed to save its state because it has a reference to its parent fragment which is removed from the manager.

Process: com.test.test, PID: 11047 java.lang.IllegalStateException: Failure saving state: ChildFragment{423c10f0 #1 id=0x7f0b0058} has target not in fragment manager: F{423c0f88}
    at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:2618)
    at android.support.v4.app.FragmentController.saveAllState(FragmentController.java:134)
    at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:571)
    at android.support.v7.app.AppCompatActivity.onSaveInstanceState(AppCompatActivity.java:515)
    at android.app.Activity.performSaveInstanceState(Activity.java:1157)
    at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1229)

I can try to go deeper and to remove the child fragment when its parent is removed but I have a feeling this is not the right way to do this, after all I think the proper way to do this is using getChildFragmentManager().

Any help, suggestions, guidelines regarding the topic will be very appreciated.

like image 716
Mojo Risin Avatar asked Jan 19 '17 12:01

Mojo Risin


1 Answers

I have changed your code like this and it is working for me, Change ActivityA like this,

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);

        FragmentManager fm = getSupportFragmentManager();
        if (fm.findFragmentById(R.id.content) == null) {
            F f = new F();
            fm.beginTransaction()
                    .add(R.id.content, f)
                    .commit();
        }
    }

And you can use your previous implementation like setTargetFragment for the ChildFragment fragment. This solved the first exception that you mentioned in the question.

like image 159
Krish Avatar answered Sep 28 '22 03:09

Krish