Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Nested Fragments Issue "java.lang.IllegalStateException: Activity has been destroyed"

As you might know Android supports Nested Fragments, also via support library with API level 17. So basically I'm trying to add nested fragments into one of the ViewPager's fragments and get familiar with this new nice feature.

Everything works as expected at the first app launch i.e I can add child fragments, navigate through those fragments, handle the back stack etc..

But the problem is that when I leave the app by clicking Back button and relaunch the app, I got the following exception most of the time, not always :

java.lang.IllegalStateException: Activity has been destroyed
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1342)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
    at com.cnlms.andnestedfragments.ui.fragments.FragWrapper.addChildFragment(FragWrapper.java:145)
    at com.cnlms.andnestedfragments.ui.fragments.FragWrapper.onActivityCreated(FragWrapper.java:96)
    at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:1468)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:931)
    at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444)
    at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:461)
    at android.support.v4.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:163)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:1012)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:881)
    at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1366)
    at android.view.View.measure(View.java:15172)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:15172)
    at android.widget.LinearLayout.measureVertical(LinearLayout.java:833)
    at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
    at android.view.View.measure(View.java:15172)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2148)
    at android.view.View.measure(View.java:15172)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1848)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1100)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1273)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:998)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4212)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
    at android.view.Choreographer.doCallbacks(Choreographer.java:555)
    at android.view.Choreographer.doFrame(Choreographer.java:525)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
    at android.os.Handler.handleCallback(Handler.java:615)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4745)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    at dalvik.system.NativeStart.main(Native Method)

The whole project is available here.

Can the activity be null at some point? I mean when I call getChildFragmentManager(), shouldn't the activity be attached to the Fragment Manager? Any suggestion will be greatly appreciated.

More details on the app,

ViewPager hosts two fragment instances: FragRegular and FragWrapper. First one is just a simple useless fragment; the second one, FragWrapper, acts as a parent fragment i.e the container for the child fragments. Here's what FragWrapper's layout looks like:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!--
        Adds nested child fragments when clicked
    -->
    <Button
        android:id="@+id/btn_go_deep"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="Go Nesty!"/>

    <!--
        Nested Fragment Container
    -->
    <FrameLayout
        android:id="@+id/frag_container"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/btn_go_deep"/>

</RelativeLayout>

So basically the first child fragment is added into FragWrapper within the onActivityCreated() callback method of the FragWrapper, and the subsequent child fragments are added at runtime, triggered with a button click. Here's what FragWrapper class looks like:

public final class FragWrapper extends BaseFragment {
    /**
     *
     *  The Wrapper Fragment that hosts nested child fragments.
     *
     *  First child fragment is added in onActivityCreated() callback
     *
     *  More child fragments can be added at runtime by clicking 'Go Nesty!'
     *  button.
     *
    */

    /**
     *  Holds back stacked fragment tags
     */
    private Stack<String> backStack;

    /**
     *  Child Fragment Manager
     */
    private FragmentManager fm;

    /**
     *  Fragment Tags
     */
    private int fragCount = 1;


    private static FragWrapper instance;

    public static FragWrapper getInstance() {

        if (instance == null) {

            instance = new FragWrapper();

        }

        return instance;

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.frag_wrapper, container, false);

        view.findViewById(R.id.btn_go_deep).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                /**
                 *  Adds nested child fragments
                 */
                fragCount+=1;

                addChildFragment(
                        FragChild.newInstance(fragCount),
                        String.valueOf(fragCount),
                        true
                );
            }

        });

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {

        super.onActivityCreated(savedInstanceState);

        /**
         *  Add Initial Fragment
         */
        addChildFragment(
                FragChild.newInstance(fragCount),
                String.valueOf(fragCount),
                false
        );

    }

    private void addChildFragment(final Fragment fragment, final String fragmentTag, final boolean addToBackStack) {

        /**
         *  initialize child fragment manager
         */
        if (fm == null) fm = getChildFragmentManager();


        /**
          *  Starts a new transaction
         */
        FragmentTransaction ft = fm.beginTransaction();


        /** 
         *  Hide lastly added fragment
         */
        if (backStack != null  && !backStack.isEmpty()) {

            ft.hide(fm.findFragmentByTag(backStack.peek()));

        }

        /** 
         *  Add new fragment
         */
        ft.add(R.id.frag_container, fragment, fragmentTag );


        /**
         *  Add to back stack
         */
         if (addToBackStack) {

            ft.addToBackStack(null);

        }

         /**
         *  Commit transaction
         */
        ft.commit();


        /**
         * Save fragment tag
         */
        if (backStack == null) backStack = new Stack<String>();

            backStack.push(fragmentTag);

    }

    public boolean popFragment() {

        /**
         *  Allow this fragment to consume the back button click
        */
        if (backStack != null   && !backStack.isEmpty()) {

            backStack.pop();

            fragCount-=1;

        }

        return fm != null && fm.popBackStackImmediate();

    }

    @Override
    public boolean backPressed() {

        return popFragment();

    }
}
like image 625
Can Elmas Avatar asked Jan 14 '13 15:01

Can Elmas


1 Answers

Someone posted another question similar and the problem is due to a bug in the ChildFragmentManager. Basically, the child FragmentManager ends up with a broken internal state when it is detached from the activity. Have a look at the original answer here

like image 179
Gomino Avatar answered Oct 21 '22 18:10

Gomino