2

I have a listener in my Activity replace a Fragment after a network request is finished on another thread. So this listener is calling a line of code like this:

getFragmentManager().beginTransaction().replace(R.id.container, fragment, fragmentTag).commit();

The commit() in this line of code occasionally throws an IllegalStateException. According to the docs,

A transaction can only be committed with this method prior to its containing activity saving its state. If the commit is attempted after that point, an exception will be thrown. This is because the state after the commit can be lost if the activity needs to be restored from its state. See commitAllowingStateLoss() for situations where it may be okay to lose the commit.

While looking into this I came across the FragmentManager.isDestroyed() method. The javadocs read:

Returns true if the final Activity.onDestroy() call has been made on the FragmentManager's Activity, so this instance is now dead.

I guess I'm just a bit confused about the implications of the FragmentManager's Activity instance being dead. When should we use FragmentManager.isDestroyed()? Would checking it before committing the replace FragmentTransaction avoid the IllegalStateException?

pumpkinpie65
  • 960
  • 2
  • 14
  • 16
  • you should post your logcat error – Kingfisher Phuoc Sep 28 '15 at 02:22
  • I have been experiencing the same thing ... and I find that it is entirely true: in all my cases where FragmentManager.isDestroyed() returns true, the FragmentTransaction.commit() will throw the IllegalStateException. Indeed, first checking .isDestroyed() will help you avoid the exception, but you won't get your Fragment transaction conducted either. – alpartis Oct 04 '15 at 05:31
  • @alpartis I'll add some better logging and see if FragmentManager.isDestroyed() returns true when I get the IllegalStateException. Your comment seems good enough to be an answer though. If you post it as one and no one posts a better one in a little while, I'll accept it. – pumpkinpie65 Oct 04 '15 at 06:15

2 Answers2

1

My code looks something like the following. When executed, things either work and my desired fragment is displayed, or I get the log message indicating that .isDestroyed() was true. If I remove the check on .isDestroyed() then ft.commit() will throw IllegalStateException.

This solution prevents the resulting crash, but it doesn't necessarily resolve the underlying timing problem of why it's coming up in the first place.

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragementTransaction;

public class CallingCard extends Fragment
{
    // ... some stuff for my fragment ...
}

public class MyFragment extends Fragment
{
    public void showCallingCard() {
        CallingCard callingCard = new CallingCard();
        FragmentManager fm = parent.getSupportFragmentManager();

        // ensure serialization of fragment transactions
        fm.executePendingTransactions();

        if (!fm.isDestroyed()) {
            // attempt to display fragment
            FragementTransaction ft = fm.beginTransaction();
            ft.replace(R.id.fragPlaceHolder, callingCard);
            ft.commit();
        } else {
            Log.e("tag", "fm.isDestroyed() was true");
        }
    }
}
alpartis
  • 1,086
  • 14
  • 29
  • In my case Fragments had not been properly/completely cleaned up from a prior use, but their `.Destroy()`, etc. methods HAD been called. Subsequently, when my fragment was presented a 2nd time, the internal state variables were all out of whack (that's a technical term) and my fragment transaction would fail because Android still thought the containing fragment was still destroyed (as indicated by the result of `.isDestroyed()`). – alpartis Oct 05 '15 at 18:07
  • Starting to get some data back from my logs and I'm still getting the crash and it looks like `fm.isDestroyed()` is returning `false`. – pumpkinpie65 Nov 10 '15 at 01:49
  • Android doesn't necessarily process fragment transactions exactly when you think in all cases. In some cases, it takes some brute force on your part to ensure proper serialization. Add a call to `.executePendingTransactions()` before beginning a new transaction. I'll try to update my answer above to reflect this. – alpartis Nov 11 '15 at 03:17
  • .executePendingTransactions() is returning false, which means there weren't any pending transactions to be executed. – pumpkinpie65 Dec 21 '15 at 18:47
0

Maintain a flag to know the Activity's current state. Check for the onPause state.

Don't make fragment manager transaction after the activity goes in onPause state. THIS WORKED FOR ME..!!!

@Override
protected void onPause() {
    super.onPause();
    isPaused = true;

}

@Override
protected void onResume() {
    super.onResume();
    isPaused = false;
}

// check for the isPaused flag.

if (!isPaused) {
        fragmentManager.popBackStack(fragment.getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
        fragmentManager.beginTransaction()
                // .setCustomAnimations(android.R.anim.fade_in,android.R.anim.fade_out)
                .replace(R.id.container, fragment)
                .commitAllowingStateLoss();
    }
M_J
  • 1
  • 1