前略: ダイアログはDialogではなくDialogFragmentで作るというめんどくささ
Android でダイアログを作ろうとすると、今は「DialogFragment で作る」というのが一般的です。ただ、調べると、DialogFragmentを使わずにAlertDialogだけでダイアログを作る記事なども見受けられると思います、、、、ここでは、ダイアログはDialogFragmentで作るのが一般的だということと、その時の注意点を。
Dialog is
ダイアログUIを提供するクラスです。代表的なサブクラスはAlertDialogやDatePickerDialogなどがあります。DialogFragmentを使う場合でも、UIを実現しているのはこれらのクラスが実態になります。
UIとしては画面上の一部で表示するのが一般的ですが、全画面として表示するようにカスタムすることも可能です。
参考
DialogFragment is
公式サイトによると、以下のように。
これらのクラスでは、ダイアログのスタイルと構造が定義されますが、ダイアログのコンテナとして DialogFragment を使用してください。DialogFragment クラスでは、Dialog オブジェクトでメソッドを呼び出す代わりに、ダイアログの作成と表示の管理に必要なすべてのコントロールが提供されます。
DialogFragment を使ってダイアログを管理すると、ライフサイクル イベント(戻るボタンを選択したときや画面を回転したときなど)が正しく処理されます。DialogFragment クラスを使用すると、従来の Fragment のように、大きな UI で埋め込み可能なコンポーネントとしてダイアログの UI を再利用することもできます(ダイアログ UI を大小の画面で異なって表示させる場合など)。
少しかいつまんで言うと↓↓↓
- ダイアログのコンテナとして DialogFragment を使ってね。
- ダイアログ制御に必要なすべてのコントロールができるよ。
- 戻るボタン押したときや画面回転したときなどのライフサイクルイベントで正しく動くよ。
DialogFragment 使わないといけないの??
「ダイアログ表示したいだけなのに、何?ダイアログ「フラグメント」って」「なんか直感的じゃないんだよな、ダイアログでいいじゃん」と思う人もいると思います。
少し歴史を紹介しますが、使わないといけないというわけではないです。が、もう Google 的には Dialog 使う場合は DialogFragment を使うという感じになってます。
- Android2系時代
当時、Dialog表示は Activity の onCreateDialog メソッドでダイアログ表示処理を書いて、 showDialog というメソッドを呼び出すことで onCreateDialog が実行され、表示していた。これが一般的だった。 ちなみに、 onCreateDialog で実装しないと画面回転時などにクラッシュするか、落ちないにしても WindowLeak エラーログが発生したりしますが、この時代、これを知らない人は少なくなかったのではないかと勝手に予想します。 - Android3時代
Fragment が作られました。合わせて DialogFragment も作られました。 - Android4.0時代
showDialog が deprecated になる。Google、 DialogFragment 推しとなる。
実際に使ってみよう😁、、、なんか、めんどくさぁっ😩
以下、DialogFragmentつまづきあるあるです。
- あるあるその1:出だしは好調。ふむふむ、ダイアログ表示するには onCreateDialog で表示したいダイアログインスタンを返せばよいのね、、ふむふむ、いいぞいいぞ、わかりやすい!
- あるあるその2:無名クラスでnewして落ちる
あれ?落ちたぞ?あ、 Fragment だから無名クラスで new しちゃいけないのか。。ちょっとしたダイアログだからいちいちクラス化するのも面倒だったんだけど。。え、ちょっとまってダイアログ作るたびにクラス化するの面倒なんだけど、、、どうしよう。。(ActivityにattachするFragmentを無名クラスでインスタンス化すると落ちる理由: 画面回転時など、Activityが再生成される場合に落ちます。ActivityはattachされていたFragmentの復元のために、attatchしているクラスに対して、リフレクションでpublicの引数なしコンストラクタを使って再生性をしようとしますが、javaの無名クラスはコンストラクタを持つことができないため、コンストラクタがないから再生成できませんよーってエラー出して落ちます。) - あるあるその3:キャンセルハンドリングの設定先を間違える。
あれ? Dialog に setCancelable とか setOnCancelListener でキャンセル系の設定してるのに効かないぞ?あれれ?・・・あ、Dialogの方じゃなくてDialogFragmentの方の setCancelable とか onCancel を使わないといけないのね。
要は、面倒くさいです。
いちいち DialogFragment を継承したクラスを作らないために
めんどくさぁ😩の項で紹介した「無名クラスでnewして落ちる」ですが、DialogFragmentもFragmentの特性を引き継いでるので無名クラスで new すると、回転処理などでActivityを再生成された場合に落ちます。それを回避するために、 DialogFragment を継承したクラス(static付きの内部クラスにするにしても)を作るわけですが、毎回必要な分だけ作るのか、どうするのか。個人的に良いと思っているのは、まず汎用的に使えるクラスを作っておいて、必要であれば別途クラスを作るというのが良いかと思っています。
これの良いところは、ダイアログの仕様に関してアプリ特化した共通処理を汎用クラスに持たせやすいことです。毎回 DialogFragment 継承クラスを作るとそれが面倒になります。(あと、大したDialogじゃないのにクラスが増えていくのもなんだか嫌です。)
作りは DialogFragment の setPositiveListener とか setNegativeListener など、 DialogFragment が持っている、インターフェースをラップした(必要な分だけでも)クラスになるので、ある程度設定できることが多くなるはずです。javaの場合はコンストラクタにすると引数が大量になるのでBuilderパターンを使うと使いやすいし、kotlinの場合はデフォルト値設定ありのコンストラクタで実装すると良い(※)と思います。※kotlinの引数は引数名を指定すれば順番とか気にしなくていいし、デフォルト設定しておけば必要な分だけ渡せば良いので、わざわざBuilderにする必要もない。
まとめ
- ダイアログを Dialog で作るなら、ちゃんと DialogFragment で作ろうね。
- DialogFragment も Fragment なので、めんどくさい癖がある。
- 汎用的に使える DialogFragment を継承したクラスを作っておくと良いかも。