DialogFragment IllegalStateException when screen goes off

Viewed 1483

First of all, I've read dozens of SO questions and read excellent post by Alex Lockwood about the IllegalStateException problems connected with fragment transactions: https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html - tl;dr I've done my homework

But what I didn't find anywhere is the IllegalStateException problem when the screen goes off and we are trying to commit fragment transaction - show DialogFragment in my case.

What is exactly happening in my app is reacting to other activity result and later on showing a DialogFragment. As suggested by many SO users, I'm not showing dialog from onActivityResult but from onPostResume (I've tried also with onResumeFragments) Simplified code looks like this

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    Timber.i("${this::class.java.simpleName} onActivityResult")
    activityResult = ActivityResult(this::class, requestCode, resultCode, data)
}

override fun onPostResume() {
    super.onPostResume()
    Timber.i("${this::class.java.simpleName} onPostResume")
    activityResult?.let { ActivityResultRepository.resultsSubject.onNext(it) }
}

override fun onResumeFragments() {
    super.onResumeFragments()
    Timber.i("${this::class.java.simpleName} onResumeFragments")
}

I couldn't reproduce exception on my device (Mate 20 pro) or on emulator but I'm receiving crash reports from users of my app (0.5% users affected)

My assumption is, my app is trying to show a dialog after screen goes off. Screen going off is an arbitrary event that causes onSaveInstanceState method to be called and it can be called right before my onPostResume method. However this is only my assumption!, maybe there is another cause of the exception

Has anyone experienced similar problems and manage to handle them? I know I can use commitAllowingStateLoss but firstly - show method of DialogFragment doesn't allow to do that, would have to show dialog manually by using FragmentManager (don't know if there are any implications), secondly commitAllowingStateLoss is generally a bad thing to do

I'm adding crash report although everyone saw it already somewhere :)

Caused by java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
       at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053)
       at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079)
       at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678)
       at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632)
       at android.support.v4.app.DialogFragment.show(DialogFragment.java:143)
3 Answers

This happens a lot. Activity is not in the correct state to show a dialog. You can guard yourself with this:

if (!isFinishing() && !isDestroyed()) {
    dialog.show();
}

I've accepted @MatPag answer altought I ended up with different solution, so I'm posting it, maybe it will help someone

In my scenario I don't care about savedInstanceState - it's perefectly fine for me to show initial state of an activity when Android is recreating it so I'm passing null to parent onCreate method:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(null) 
}

Later on, when showing a dialog, I'm catching IllegalStateException that may be thrown, I've created helper method for this:

private fun DialogFragment.showAllowingStateLoss(manager: FragmentManager?, tag: String) {
    try {
        this.show(manager, tag)
    } catch (e: IllegalStateException) {
        // no-op
    }
}

Activity class has method isFinishing(), link to method. You could easily check:

if (!this.isFinishing)
    loadingDialog.show()

An example above is opening plain dialog, but you could use it with DialogFragment.

Related