DialogFragmentとAlertDialogと画面遷移

Kenji Abe
5 min readAug 24, 2021

DialogFragment の内部でシンプルな AlertDialog を使っていて、さらにその AlertDialog のボタンから画面遷移の処理をしているときの注意点についてです。

簡単な例

簡単な実装例です。
画面のボタンを押したらダイアログを表示して、そのダイアログのボタンが押されたら、ダイアログを閉じて次の画面に遷移するのを想定してます。

まずは DialogFragment の実装です。
ダイアログの結果の通知に共有の ViewModel を使っていますが、 setFragmentResultListener でも同様です。

AlertDialog は通常ではボタン押下後に自動でダイアログが閉じるようになってるので、明示的にダイアログを閉じるようなことはしていません。

次に呼び出し元の Fragment の実装です。

実際の結果

先程の実装では実は問題あります。

ダイアログのボタンを押して、画面遷移し、そのあとにバックキー等で戻ってくると再びダイアログが表示されます。

(このgifわかりにくいですね。。。)

原因

ダイアログのボタンで画面遷移をしない通常のときと、ダイアログのボタンで画面遷移をするときの動作を見ると原因が分かります。

通常の動作

画面遷移をせずに普通にダイアログが閉じた場合の動作についてです。

DialogFragment で保持している AlertDialogsetOnDismissListener で閉じたときのイベントが実装されています。
source code )

この setOnDismissListener が起きたタイミングで、 onDismiss() が呼び出され、最終的に dismissInternal() が呼び出されます。
( source code )

この dismissInternal() で、FragmentTransaction#removeDialogFragment を削除しています。
( source code )

以上の動作から AlertDialog が正常に閉じられれば、 DialogFragmentFragmentManger から削除されることになります。

まとめると、
1. ダイアログのボタンを押す
2. ダイアログが閉じる
3. 閉じるときにダイアログの setOnDismissListener が実行される
4. FragmentManager から DialogFragment が破棄される

という感じです。

画面遷移したときの動作

では、今回のように AlertDialog のボタンイベントで画面遷移をした場合の動作について解説します。

まず知っておいてほしいのが、 DialogFragment が表示されていても呼び出し元のライフサイクルとしては、 ON_RESUME の状態になります。戻り値の処理を今回のような ViewModel を使った場合も setFragmentResultListener 使った場合も、 DialogFragment が閉じる前に即実行されます。

上記の理由から、DialogFragment が保持してる AlertDialog が破棄される前に画面遷移が行われていることになります。

先に、 DialogFragmentonDestroyView が呼び出され、その中で内部で持っている AlertDialogdimiss() でダイアログを閉じています。ただし、この直前で setOnDismissListener をnullにしています。
( source code )

最終的には、 dismissInternal() が呼び出されないため FragmentManger から破棄されません。

まとめると
1. ダイアログのボタンを押す
2. イベント通知で即座に画面遷移処理
3. onDestroyView でダイアログを閉じる。
4. (DialogFragment 自体は破棄されない)

そのため、画面遷移後に戻ったときに DialogFragment が破棄されていないため再表示されることになります。

この挙動は画面回転とほぼ同じ感じになります。

対応

対応としては簡単で、ボタンを押したときに画面遷移前にちゃんとダイアログを閉じてあげるだけです。

補足

DialogFragment を表示するときは childFragmentManager を使うようにしましょう。

https://developer.android.com/guide/fragments/dialogs#showing

When creating a DialogFragment from within a Fragment, you must use the Fragment's child FragmentManager to ensure that the state is properly restored after configuration changes.

実は今回の紹介した問題は、 parentFragmentManager を使うと起きないです。ドキュメントに書いてる通り、 childFragmentManager を使った場合はしっかりとrestoreが動くので今回の現象に繋がります。

--

--

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO