10

I have a parent Fragment Activity that has a ViewPager which contains a child ViewPager. The child ViewPager contains Fragments for each page. I communicate between these child page fragments and the top parent Fragment Activity using a callback interface e.g.

public interface Callbacks {
    public void onItemSelected(Link link);
}

In the parent Fragment Activity I listen for onItemSelected events e.g.

@Override
public void onItemSelected(Link link) {
    Bundle argumentsFront = new Bundle();
    argumentsFront.putParcelable(FragmentComments.ARG_ITEM_ID, link);
    fragmentComments = new FragmentComments();
    fragmentComments.setArguments(argumentsFront);
    getSupportFragmentManager().beginTransaction().replace(R.id.post_container, fragmentComments).commitAllowingStateLoss();
}

Now this works fine when the app is first launched.

If you turn the device to change the orientation the Activity restarts. All fragments reinitialise themselves as I use setRetainInstance(true); (I do not call setRetainInstance(true) in the page Fragments of the child ViewPager as it is not supported). However if I click a list item in the Fragment of the child ViewPager I get this exception:

FATAL EXCEPTION: main
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.commitAllowingStateLoss(BackStackRecord.java:578)

Does anyone know why this happens?

Thanks

Milo
  • 1,017
  • 3
  • 16
  • 22

5 Answers5

11

When you rotate the device, Android saves, destroys, and recreates your Activity and its ViewPager of Fragments. Since the ViewPager uses the FragmentManager of your Activity, it saves and reuses those Fragments for you (and does not create new ones), so they will hold the old references to your (now destroyed) original Activity, and you get that IllegalStateException.

In your child Fragments, try something like this:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.v(TAG, "onAttach");

    // Check if parent activity implements our callback interface
    if (activity != null) {
        try {
            mParentCallback = (Callbacks) activity;
        }
        catch (ClassCastException e) {
        }
    }
}

Then when a selection occurs:

if(mParentCallback != null) {
    mParentCallback.onItemSelected(selectedLink);
}

Since onAttach gets called as part of the Fragment lifecycle, your Fragments will update their callback reference on rotation.

Steven Byle
  • 13,149
  • 4
  • 45
  • 57
  • Unfortunately this doesn't work. My `onAttach` method already looks like the one you provided. On orientation change my `onAttach` method is not called again and as a result my callback is not being updated. The parent Fragment has `setRetainInstance(true)`, I guess this prevents this child Fragment from being detached? – Milo Feb 12 '13 at 22:45
  • Hmm according to the documentation `onAttach` should still be getting called even if `setRetainInstance(true)` is set, so something else may be going wrong here. Could you post more of your code around setting and calling your callback? Also, check out the answer to this question, using `setRetainInstance(true)` is not a good practice in this situation, you should be saving and restoring state - http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean – Steven Byle Feb 12 '13 at 22:57
  • 3
    I found a solution. Because I had nested Fragments like this: (FragmentActivity -> Fragment 1 -> Fragment 2 (with ViewPager) -> ViewPager Child Fragments) I had to move the callbacks to Fragment 1 (where onAttach and onDetach were being called). I then made a static call from the `onItemSelected` method in the ViewPager's child Fragments to the callback in the parent Fragment (Fragment 1). This seems a bit of a hack but works well. I use `setRetainInstance(true)` in Fragment 1 so the child Fragments retain state too. – Milo Feb 13 '13 at 09:21
  • 2
    Ahh I see so your architecture is a bit more complex. I have done something similar this before, where a `Fragment` calls back to its parent `Fragment`. I used the same code as above in onAttach, but instead of casting the parent `Activity` to my callback, I casted `getParentFragment()` to get a ref to the parent `Fragment` who implemented the callback. Maybe that could help make a cleaner solution, but glad you found one either way. – Steven Byle Feb 13 '13 at 13:46
  • That does indeed sound a better solution I'll try that instead of relying on static calls. Thanks for the help. – Milo Feb 13 '13 at 15:46
  • 1
    Thanks a lot guys. At first I was confused why this was the accepted answer, but comment 3 and 4 helped a lot. This is what I had: Parentfragment -> viewpager with fragments - > child fragment. I put a listener in viewpager fragments to ParentFragment, using getParentFragment in onAttach, then a second listener to pass that data on in parentfragment to fragmentactivity. I think that is what Steven meant. Now I can get everything to attach correctly:) – Karl Jan 07 '14 at 20:10
  • I got a similar problem please can you take a look at it too? http://stackoverflow.com/q/38229443/6181476 – X09 Jul 06 '16 at 19:05
0

I had a similar issue, I think it is because the fragments are retained and are keeping a reference to a destoryed activity, my solution was to keep a reference to the fragment in the activity e.g Fragment myfragment = null. And then use the following code in MyFragment:

    public void onAttach(Activity activity) {
        super.onAttach(activity);
        ((TestActivity)activity).contentFragment = this;
}
user993553
  • 1,077
  • 5
  • 12
  • Could you explain this a little further, I'm not sure I understand fully. Do you keep a reference to every child Fragment in the parent fragment Activity? My child Fragment is a ViewPager which contains many child fragment pages so I don't want to to keep static copies of all these if I can help it. Thanks. – Milo Feb 12 '13 at 22:49
0

Had a similar issue. Basically if the ViewPager just has couple of fragments, then store references to them in current activity. DO NOT call pagerAdapter's getItem() because it creates a new fragment and it is not attached to any activity and that's why we see "Activity has been destroyed" exception. If you don't want to keep fragment references, then you can use findViewWithTag() method to get Fragment object.

andude
  • 500
  • 1
  • 5
  • 11
0

Committing transactions in OnPostResume callback fixed the issue for me. Thanks to following blogpost http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

@Override
protected void onPostResume() {
    super.onPostResume();
    // Commit your transactions here.
}
Varun Bhatia
  • 4,326
  • 32
  • 46
0

I had this problem with nested fragments and none of the stackoverflow solutions worked for me. Just it seems, that there is a bug with support library, when dismissed fragments still store pointers to previous activity (so getFragmentManager() just returns null, because it is called on already destroyed activity), that's why you need to manage pointers yourself. I ended up with a following solution:
1. In the first level fragment I was saving pointer to the activity in the method

public void onAttach(Activity activity) {
        super.onAttach(activity);
        parentActivity = activity; // parentActivity is static variable
}

2. In the activity which handles fragments I ended up with this code:

private void launchFragment(Fragment fragment, Activity parent) {
            FragmentTransaction transaction;
            if(parent == null)
                transaction = mFragmentManager.beginTransaction();
            else    // for nested child fragments, workaround for Android parent pointer bug
                transaction = parent.getFragmentManager().beginTransaction();
            transaction.replace(R.id.container, fragment);
            transaction.addToBackStack(null);
            transaction.commit();
}

You should pass parentActivity of FIRST level fragment only when you are calling SECOND level (nested) fragments, as it seems that this bug is only with nested ones after you bring your app from foreground.

Bio-Matic
  • 793
  • 1
  • 8
  • 20