AlertDialogのカスタマイズその1：引数を渡そう

何かと便利な`AlertDialog`ですが、引数を渡してその内容を表示させることも簡単にできます。
ViewBindingを使い柔軟にカスタマイズすることもできますが、今回はシンプルにテキストのみを呼び出し元から渡すサンプルを実装してみたいと思います。

# AlertDialogについて
基本的にAndroidのDialogは`タイトル`、`コンテンツエリア`、`PositiveButton`、`NegativeButton`、`NeutralButton`といった部品で構成されています（ボタンの総称を`ActionButton`とも言います）。
<img width="300" alt="ダイアログサンプル.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/5b905ea2-070b-9fd9-60cd-a79a5c39d721.png">
Dialogとしては`AlertDialog`が最もよく使われ、Alertとはありますが別に警告に限らず様々な用途で使うことができます。

今回は`MainAcitivty`で`MaterialAlertDialogBuilder()`を使用する方法と、`DialogFragment`を使う方法の2種類を紹介します。

# 作成するサンプル
`ListView`に英単語の一覧を表示し、アイテムをクリックすると`AlertDialog`が起動し、その単語の品詞、意味を表示するサンプルを作成します。
なお`ListView`のカスタマイズについては本記事では触れません。
![単語帳.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/347a0af4-2a88-6185-fbc8-4a6038f95581.gif)

なお今回紹介する2つの方法どちらも同じ動きをします。

# コード
それでは両者のコードを見てみましょう。

## MaterialAlertDialogBuilder()で実装

たぶん一番楽なやり方です。

#### MainActivity.kt

```kotlin
class MainActivity : AppCompatActivity() {

    // （省略）

    private inner class ListItemClickListener : AdapterView.OnItemClickListener{
        // リストのアイテムがクリックされたときのイベントハンドラ
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            val item = parent?.getItemAtPosition(position) as MutableMap<String, Any>
            val wordName = item["name"] as String
            val wordSpeech = item["speech"] as String
            val wordMeaning = item["meaning"] as String

            val builder = MaterialAlertDialogBuilder(this@MainActivity)
            builder.setTitle(wordName)
            builder.setMessage("$wordSpeech：$wordMeaning")
            val dialog = builder.create()
            dialog.show()
        }
    }
}
```

シンプルですね。
`MaterialAlertDialogBuilder`に`context`を渡し`builder`オブジェクトを生成します。
生成したオブジェクトに`setTitle()`、`setMessage()`で値を設定するだけですね。
ただまぁ「引数を渡す」ってのとはちょっとイメージが違うか。
今回は触れていませんがもちろん各ボタンがクリックされた場合の処理も`MainAcitivty`内部に記述できます。

## DialogFragmentで実装

ちゃんと（？）`DialogFragment`を生成し、そこに引数を渡します。
拡張性はこちらの方が高いですね。

#### WordDialogFragment.kt

```kotlin
class WordDialogFragment : DialogFragment() {

    // 表示する「単語」、「品詞」、「意味」のプロパティ
    private var _word: String = ""
    private var _speech: String = ""
    private var _meaning: String = ""

    companion object{
        // インスタンス生成のメソッド
        // 引数として読みだし元から「単語」、「品詞」、「意味」を受け取る
        fun create(word: String, speech: String, meaning:String): WordDialogFragment{
            val bundle = Bundle()
            bundle.putString("Word", word)
            bundle.putString("Speech", speech)
            bundle.putString("Meaning", meaning)
            val dialog = WordDialogFragment().also { it.arguments = bundle }
            return dialog
        }
    }

    // 引数の内容をプロパティに設定
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _word = arguments?.getString("Word") as String
        _speech = arguments?.getString("Speech") as String
        _meaning = arguments?.getString("Meaning") as String
    }

    // ダイアログの表示
    // プロパティから画面部品に値を設定
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog =  activity?.let{
            val builder = AlertDialog.Builder(it)
            builder.setTitle(_word)
            builder.setMessage("$_speech：$_meaning")
            builder.create()
        }
        return dialog ?: throw IllegalStateException("Error")
    }
}
```

すこし注意していただきたいのがメンバ変数にあたる`_word`、`_speech`、`_meaning`のデータの持ち方（与え方？）です。
コンストラクタの引数としてこれらのデータを受け取り、`onCreateDialog`で`setTitle()`しようとすると**画面回転させたときにアプリが落ちます**。

というのも`DialogFragment`（実際にはそれ以外にも`Fragment`を継承しているクラスも）は、画面を回転させたりするとActivityが破棄され、そしてその都度再度生成される動きとなっているからです。
そしてその際の初期化は**引数なしのコンストラクタで行われ**ます。
なので落ちちゃうんですね。

というわけで今回のサンプルでは`onCreate`で引数をメンバ変数に設定し、`onCreateDialog`でタイトル、メッセージにsetするという書き方をしています。

#### MainActivity.kt
Dialogの生成は以下の通りです。

```kotlin
class MainActivity : AppCompatActivity() {

    // （省略）

    private inner class ListItemClickListener : AdapterView.OnItemClickListener{
        // リストのアイテムがクリックされたときのイベントハンドラ
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            val item = parent?.getItemAtPosition(position) as MutableMap<String, Any>
            val wordName = item["name"] as String
            val wordSpeech = item["speech"] as String
            val wordMeaning = item["meaning"] as String

            // ダイアログの生成
            var dialogFragment = WordDialogFragment.create(wordName, wordSpeech, wordMeaning)
            dialogFragment.show(supportFragmentManager, "WordDialog")
        }
    }
}
```

こちらは`DialogFragment`を呼び出しているだけですね。
`create()`メソッドで引数を渡すのだけ忘れないでください。


# まとめ
シンプルに引数を渡して表示するだけの内容ですが、`DialogFragment`側でのデータの持ち方に少し工夫が必要ですね。
次はDialogから引数を受け取る方法についてまとめてみたいと思います。