DialogFragment
の内部でシンプルな AlertDialog
を使っていて、さらにその AlertDialog
のボタンから画面遷移の処理をしているときの注意点についてです。
簡単な例
簡単な実装例です。
画面のボタンを押したらダイアログを表示して、そのダイアログのボタンが押されたら、ダイアログを閉じて次の画面に遷移するのを想定してます。
まずは DialogFragment
の実装です。
ダイアログの結果の通知に共有の ViewModel
を使っていますが、 setFragmentResultListener
でも同様です。
AlertDialog
は通常ではボタン押下後に自動でダイアログが閉じるようになってるので、明示的にダイアログを閉じるようなことはしていません。
次に呼び出し元の Fragment
の実装です。
実際の結果
先程の実装では実は問題あります。
ダイアログのボタンを押して、画面遷移し、そのあとにバックキー等で戻ってくると再びダイアログが表示されます。
(このgifわかりにくいですね。。。)
原因
ダイアログのボタンで画面遷移をしない通常のときと、ダイアログのボタンで画面遷移をするときの動作を見ると原因が分かります。
通常の動作
画面遷移をせずに普通にダイアログが閉じた場合の動作についてです。
DialogFragment
で保持している AlertDialog
に setOnDismissListener
で閉じたときのイベントが実装されています。
( source code )
この setOnDismissListener
が起きたタイミングで、 onDismiss()
が呼び出され、最終的に dismissInternal()
が呼び出されます。
( source code )
この dismissInternal()
で、FragmentTransaction#remove
で DialogFragment
を削除しています。
( source code )
以上の動作から AlertDialog
が正常に閉じられれば、 DialogFragment
が FragmentManger
から削除されることになります。
まとめると、
1. ダイアログのボタンを押す
2. ダイアログが閉じる
3. 閉じるときにダイアログの setOnDismissListener
が実行される
4. FragmentManager
から DialogFragment
が破棄される
という感じです。
画面遷移したときの動作
では、今回のように AlertDialog のボタンイベントで画面遷移をした場合の動作について解説します。
まず知っておいてほしいのが、 DialogFragment
が表示されていても呼び出し元のライフサイクルとしては、 ON_RESUME
の状態になります。戻り値の処理を今回のような ViewModel
を使った場合も setFragmentResultListener
使った場合も、 DialogFragment
が閉じる前に即実行されます。
上記の理由から、DialogFragment
が保持してる AlertDialog
が破棄される前に画面遷移が行われていることになります。
先に、 DialogFragment
の onDestroyView
が呼び出され、その中で内部で持っている AlertDialog
の dimiss()
でダイアログを閉じています。ただし、この直前で 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 aFragment
, you must use theFragment
's childFragmentManager
to ensure that the state is properly restored after configuration changes.
実は今回の紹介した問題は、 parentFragmentManager
を使うと起きないです。ドキュメントに書いてる通り、 childFragmentManager
を使った場合はしっかりとrestoreが動くので今回の現象に繋がります。