[Android] Dialog show()에서 IllegalStateException 발생(Fragment commit)

http 응답을.. async하게 처리하게 되다보니.. 문제가 더 많이 발생하네요.

 

Dialog show()에서.. 간헐적으로 아래의 에러가 발생했습니다.

 

java.lang.IllegalStateException:

at androidx.fragment.app.FragmentManager.checkStateLoss (FragmentManager.java:1819)

at androidx.fragment.app.FragmentManager.enqueueAction (FragmentManager.java:1859)

at androidx.fragment.app.BackStackRecord.commitInternal (BackStackRecord.java:321)

at androidx.fragment.app.BackStackRecord.commit (BackStackRecord.java:286)

at androidx.fragment.app.DialogFragment.show (DialogFragment.java:175)

at com.xxx.yyy.ActivityA$4.onResponse (ActivityA.java:404)

at okhttp3.RealCall$AsyncCall.run (RealCall.kt:140)

at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)

at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)

at java.lang.Thread.run (Thread.java:764)

 

 

대충.. checkStateLoss 코드르 살펴보니 아래와 같네요.

    private void checkStateLoss() {
        if (isStateSaved()) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
    }

결국... isStateSaved()함수를 봐야합니다.

    /**
     * Returns {@code true} if the FragmentManager's state has already been saved
     * by its host. Any operations that would change saved state should not be performed
     * if this method returns true. For example, any popBackStack() method, such as
     * {@link #popBackStackImmediate()} or any FragmentTransaction using
     * {@link FragmentTransaction#commit()} instead of
     * {@link FragmentTransaction#commitAllowingStateLoss()} will change
     * the state and will result in an error.
     *
     * @return true if this FragmentManager's state has already been saved by its host
     */
    public boolean isStateSaved() {
        // See saveAllState() for the explanation of this.  We do this for
        // all platform versions, to keep our behavior more consistent between
        // them.
        return mStateSaved || mStopped;
    }

 

음...

스택오버플로우에서 문제가 왜 발생했는지 찾아봤습니다.

https://stackoverflow.com/a/25706263/7225691

 

onSaveInstanceState() 이후에 Fragment commit()을 하면 이 문제가 생긴다고 합니다.

(DialogFragment에선 show()를 하면 문제가 생김)

 

안드로이드는 Fragment 상태를 onSaveInstanceState()에서 저장합니다. 그래서 onSaveInstanceState() 이후에

commit()을 하면 fragment 상태가 없어진다고 하네요..

 

만약 Activity가 죽고 이후에 다시 만들어졌을 때, 그 Fragment는 Activity에 추가되지 않고 이는 나쁜 사용자 경험이 됩니다. 이게 안드로이드가 무슨 수를 쓰더라도.. state loss를 허락하지 않는 이유입니다.

 

삽질을 좀 한 결과.. 저의 경우에.. 문제가 발생되는 케이스를 발견했습니다.

제 코드는 Activity에서 okhttp를 통해 HTTP request를 하고.. async한 콜백을 통해 response를 받습니다. 이 response를 받으면서 DialogFragment의 show()를 하게 되어있죠. 

보통은 request를 하고.. 바로 response를 받기 때문에 문제가 재현되지 않습니다.

 

문제를 재현하려면.. 이번에도 Thread.sleep(3000)함수를 사용해야 합니다.

response에서 응답을 받아도 바로 DialogFragment show()를 하지 말고.. 3초 후에 show()가 되게 만드는거죠.

그.. 3초가 지나기 전에 홈 키를 눌러서 홈화면으로 가게 되면 Activity의 onSaveInstanceState()가 자동으로 호출됩니다.

그리고 곧 3초가 되면서 이제 DialogFragment show()가 호출되는데.. 이 때 바로 IllegalStateException이 발생됩니다.

 

왜냐하면.. 다시 그 앱을 실행했을 때 onSaveInstanceState()을 통해 저장했던 액티비티 관련 상태(DialogFragment show()이전에 저장됐기에 DialogFragment에 대한 정보는 없음)들을 복구하게 되는데.. 만약 IllegalStateException이 발생하지 않는다면....액티비티가 복구되어도 그.. DialogFragment는 복구가 되질 않습니다.

 

해결 방법은 각자 상황에 따라 다를 수 있습니다.

저는 어차피.. 홈화면에서 앱이 다시 실행되면.. 그 Dialog가 새로 뜨도록 되어있습니다. 그래서 그냥.. 문제가 되는 Exception은 무시하게 만들었습니다.

 

try {
    myDialog.show(getSupportFragmentManager(), "MY_DIALOG");
}catch (Exception e){
    //Exception is ignored.
}

참고

https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

 

 

작성자

Posted by 드리머즈

관련 글

댓글 영역