8

Context: I have an Activity with a Fragment and 3 InnerFragments. When the Fragment onDestroy() is called, I want to remove the inner fragments from the FragmentManager. The code from onDestroy() is below.

Problem: FragmentManager throws NullPointerException, probably when commitAllowingStateLoss() is called. I don't understand why.

@Override
public void onDestroy()
{
    super.onDestroy();
    if (getFragmentManager().findFragmentById(R.id.fragment_framelayout_left) != null)
    {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.remove(mLeftFragment);
        fragmentTransaction.commitAllowingStateLoss();
    }
}

Stack trace:

02-11 12:15:14.162: E/AndroidRuntime(25911): FATAL EXCEPTION: main
02-11 12:15:14.162: E/AndroidRuntime(25911): java.lang.NullPointerException
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1419)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:429)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.os.Handler.handleCallback(Handler.java:725)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.os.Handler.dispatchMessage(Handler.java:92)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.os.Looper.loop(Looper.java:137)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at android.app.ActivityThread.main(ActivityThread.java:5039)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at java.lang.reflect.Method.invokeNative(Native Method)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at java.lang.reflect.Method.invoke(Method.java:511)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
02-11 12:15:14.162: E/AndroidRuntime(25911):    at dalvik.system.NativeStart.main(Native Method)
Bogdan Zurac
  • 6,348
  • 11
  • 48
  • 96
  • 1
    I'm not following your architecture completely. Do the `InnerFragments` live in the `Fragment`? If so, I would think you should have the `getChildFragmentManager()` managing them (not the `Activity`'s `FragmentManager`). That way, when the `Fragment` destroys, so does its children `InnerFragments`, without this extra code. – Steven Byle Feb 12 '13 at 04:14
  • Yes, the Inner Fragments live inside the Fragment (at least on tablets). On phones, they live inside an Activity (InnerFragment1 inside Activity1, InnerFragment2 inside Activity2). That's why I was using that code. Would it also work with getChildFragmentManager() in Activities ? LE: Stupid question, sorry. I will try your idea in a few hours and see how it goes. – Bogdan Zurac Feb 12 '13 at 12:44
  • 1
    `getChildFragmentManager()` is only in `Fragments`, as `Activities` cannot access children `FragmentManagers`. However, `Fragments` can call `getFragmentManager()` and access their parent `Activity`'s `FragmentManager`, allowing them to start new `Fragments` on top of themselves without needing their parent `Activity` to do it for them. – Steven Byle Feb 12 '13 at 14:23
  • Can you provide the code u rewrote for this? I have the same problem, but I am not following what Steven suggested one do to fix this. – Ted Feb 05 '14 at 11:34

3 Answers3

9

The FragmentManager manages all Fragments at the Activity level, and their lifecycle will be tied to that parent Activity. The child Fragment manager manages all Fragments at the Fragment level, and their lifecycle will be tied to that parent Fragment.

So for your phone architecture, add your InnerFragment to your Activity using getFragmentManager(). When the Activity destroys for good (via back button / finish()), the FragmentManager will destroy and release the InnerFragment for you.

For your tablet architecture, add your InnerFragments to your Fragment using getChildFragmentManager() (in the latest support library). When the Fragment destroys for good, the FragmentManager will destroy and release the InnerFragments for you.

You should not have to manage releasing and destroying your Fragments yourself. I'd recommend logging the lifecycle events of your Activities and Fragments so you can watch them go through their states and ensure correct behavior.

Steven Byle
  • 13,149
  • 4
  • 45
  • 57
  • Awesome. Thanks a lot. This solved it. One slight problem. If I use ChildFragmentManager, I can't setRetainInstance(true) on the child Fragments. Is there a workaround ? Or why should it behave like this ? – Bogdan Zurac Feb 13 '13 at 09:37
  • 1
    Using `setRetainInstance(true)` is actually already a workaround. The Google suggested approach is to save any variable state in `onSaveInstanceState()` and restore it in `onCreate` and `onCreateView`. At any time, the OS may run low on memory and need to destroy your `Fragments` and/or `Activities`, so it is their responsibility to be able to save how they currently are, and restore that state to appear to the user as nothing has changed when they return. Here's how Google recommends you save state http://developer.android.com/training/basics/activity-lifecycle/recreating.html. – Steven Byle Feb 13 '13 at 14:09
  • 1
    And here's some more info about `setRetainInstance(true)` - http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean – Steven Byle Feb 13 '13 at 14:10
  • Yeah, I knew how to save the state for Activities. For fragments I always used setRetainInstance. But I'll have a look into it. Thanks a lot mate ! You've been more than helpful ! – Bogdan Zurac Feb 13 '13 at 14:28
  • 1
    Ah good, well saving state for `Fragment`s is very similar to `Activity`s. The trick to remember is to restore your variables in `onCreate` (not `onCreateView`) when `savedInstanceState != null`, and then setup your views and set those restored values to them in `onCreateView`. If you have your `Fragment`s on a back stack, when you hit back to return to them, `onCreateView` gets called, but `onCreate` does not. So if you try to restore/default variables in `onCreateView` (like I first did), things will get messed up. Hope this helps. – Steven Byle Feb 13 '13 at 14:48
1

The NullPointerException is caused by the fact that Activity's Handler is unset from the FragmentManager, so a "solution" that will prevent the crash is the following:

public void onDestroy(){
        super.onDestroy();
        try {
            Field mActivityField = getFragmentManager().getClass().getDeclaredField("mActivity");
            mActivityField.setAccessible(true);
            mActivityField.set(getFragmentManager(), this);

            Field mPendingActionsField = getFragmentManager().getClass().getDeclaredField("mPendingActions");
            mPendingActionsField.setAccessible(true);
            mPendingActionsField.set(getFragmentManager(), null);


            Field f = Activity.class.getDeclaredField("mHandler");
            f.setAccessible(true);
            Handler handler = (Handler) f.get(this);
            handler.close();
        } catch (Throwable e) {

        }
}
Gorbas
  • 82
  • 1
  • 3
0

CASE: When you need to call Fragment(Child fragment) from another Fragment(Parent Fragment)

always use getChildFragmentManager() instead of getFragmentManager inside your Parent Fragment.

Read documentation