概要
- アプリがバックグラウンド時にFragmentを追加・切替をすると落ちる・
- 回避する一つの方法として、
- 親のFragmentがアタッチされているか判定。
-
FragmentTransaction#commitAllowingStateLoss()
を使用してFragmentの状態を無視してcommitする。
Fragmentの状態を無視して実行しているのでonSaveInstanceStateを気にしている場合はこの限りではないです。
詳細
アプリを使っていると非同期でデータを取得したタイミングで何か事を起こすことがあると思います。
私はよくそのタイミングでDialogFragmentを使っているのですが(これがよくない原因ですが)、DialogFragmentに限らずFragment全般に言えることで、アプリがバックグラウンド時にFragmentを追加や切替などを行うと大体llegalStateException
が発生します。
原因は後述するスタックトレースを見てもらうとわかるのですが、Fragment#onSaveInstanceState
を通った後にcommitを行うと「セーブしたのに操作しないでよ!!」って怒られてクラッシュします。
回避する方法としてはこういうやり方もあるようですが、自分の場合、Activityに保存されたものを再利用しないので無理矢理FragmentTransaction#commitAllowingStateLoss()
を使用してDialogFragmentを表示してます。
私が回避した方法
// fragment -> DialogFragmentを呼び出し元のFragment
// fragmentがActivityにアタッチされてるか判定
if (fragment.isAdded) {
// fragment.show(fragment.childFragmentManager, fragment.tag)
fragment.childFragmentManager.beginTransaction()
.add(dialogFragment, fragment.tag)
.commitAllowingStateLoss() // Activityの保存状態を気にせずにcommitする
}
これで一応非同期処理が終わったタイミングでアプリがバックグラウンドにいてもクラッシュすることはなくなるはずです。
試したけど駄目だったパターン
if (fragment.isAdd)
fragment.show(fragment.childFragmentManager, fragment.tag)
// もしくは
if (!fragment.isStateSaved)
fragment.show(fragment.childFragmentManager, fragment.tag)
アプリがバックグラウンドかどうか判定してもらってバックグラウンドなら表示しなくていいかって思ったけど甘くなかった。この処理はまずい。
結局Activityの状態に関係なくcommitさせるFragmentTransaction#commitAllowingStateLoss()
を利用するに落ち着きました。
終わりに
他にいい方法もあるとは思うのですが、ViewModelから操作しているという面倒パターンを組んでしまっているので、これが一番手っ取り早いかなって思います。
てか、Firebase Realtime Databaseでデータ量の少ないものをフェッチしてるから、ほぼほぼフォアグラウンドで動作するんですが、たまに(発生率は0.05~0.1%くらいで)バックグラウンドで動作しちゃうので対策したほうがいいなって修正した次第です。
これで問題あればまた考えなきゃいけないのは正直めんどくさい。
起きたエラーのスタックトレース
Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844)
at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884)
at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329)
at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294)
at androidx.fragment.app.DialogFragment.show(DialogFragment.java:260)
at com.sample.sample.ui.main.MainViewModel$checkLatest$1.onDataChange(MainViewModel.java:203)
at com.google.firebase.database.Query$1.onDataChange(Query.java:191)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6801)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)