【Kotlin研修2日目】Adapterを用いた可変リストデータのリストへの紐付けとダイアログ表示

# Adapterクラス
`アクティビティ`で記述した`リストデータ`をリスト形式ビュー(`ListView`/`Spinner`)に紐づけるクラス。
リスト形式ビューに反映する`リストデータ`を管理し、リスト形式ビューの各`Item`に当てはめる役割を担う。

## 主なAdapterクラス

|クラス|リストデータ型|
|:--|:--|
|`ArrayAdapter`|`Array`/`MutableList`|
|`SimpleAdapter`|`MutableList<MutableMap<String, *>>`<br>`XML`/`JSON`データの解析結果を格納|
|`SimpleCursorAdaptor`|`Cursor`オブジェクト<br>Android端末内DBに対する`SELECT文`の結果を格納|

# 可変リストデータとListViewの紐づけ
`可変`のリストデータを`ListView`に紐づける手順は以下の通り。

> 1. 可変のリストデータを用意
> 2. リストデータを基にAdapterオブジェクトを生成
> 3. `ListView`の`adapter`プロパティに、2.で生成したAdapterオブジェクトを代入

また、`可変`のリストデータをリスト形式ビューに反映する場合は、
`XML`で`strings.xml`に記述せず、`Kotlin`で`.kt`ファイルに記述する必要がある。

## リストデータをもつArrayAdapterオブジェクトの生成

### 定義
[参考: ArrayAdapter](https://developer.android.com/reference/kotlin/android/widget/ArrayAdapter#ArrayAdapter(android.content.Context,%20kotlin.Int,%20kotlin.Array))

```kotlin
ArrayAdapter(context: Context, resource: Int, objects: Array<T>)
// パラメータ
// context: コンテキストとなるアクティビティオブジェクト
// resource: リスト形式ビューの各Itemのレイアウトを表現するR値
// objects: リスト形式ビューに反映するリストデータ
```

### サンプルコード

```kotlin:MainActivity.kt
// リストデータの定義
val menuList = mutableListOf("item1", "item2", "item3", ...)

// 定義したリストデータをもつAdapterオブジェクトを生成
val adapter = ArrayAdapter(
    this@MainActivity,
    android.R.layout.simple_list_item_1,
    menuList
)
```

#### R値
`res`フォルダ内のファイルや、ファイル中に記述された`リソース`を識別するための`Rクラス`の`定数`。
`Java`の`int型定数`によって表現される。

#### android.R
`Android SDK`で用意された`リソース`を識別するクラス。

`android.R`クラスを`インポート文`で宣言すると、同名であるアプリ内の`R`クラスが読み込まれなくなるため、
`android.R`クラスの`R値`を利用する際はインポートせず、`android.R.<R値>`のように記述する。

## ListViewに対するArrayAdapterオブジェクトのセット
`ListView`の`adapter`プロパティに、生成した`ArrayAdapter`オブジェクトを代入する。

### サンプルコード

```kotlin:MainActivity.kt
// Adapterがリストデータを紐づけるオブジェクト(=ListView)の定義
val lvMenu = findViewById<ListView>(R.id.lvMenu)

// ListViewにAdapterオブジェクトをセット(adapterプロパティにAdapterオブジェクトを代入)
lvMenu.adapter = adapter
```

***

# ダイアログ(Dialog)
[参考: ダイアログ](https://developer.android.com/guide/topics/ui/dialogs?hl=ja)
ユーザに対して`処理続行の確認`や`注意喚起`、`追加情報の入力`を求める`ポップアップウィンドウ`。
`AlertDialog`クラスによって定義される。

## ダイアログの画面構成
![ダイアログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/52d29b0d-af06-af4b-e446-e82b2ba66798.png)

### ダイアログの最小構成
`ダイアログ`を構成するための最小限の部品は以下の2つ。

- コンテンツエリア
- アクションボタン x1(`Positive Button`)

## ダイアログの生成

### ダイアログ生成クラスの作成
`java/<PackageName>`配下に`Kotlin Class/File`を作成し、作成した`.kt`ファイルに記述する。

> `ダイアログ`は汎用性が高いため、`private`なメンバクラスでなく`トップレベルクラス`で宣言する。

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment

// ダイアログを生成するクラス(DialogFragment()を継承)
class OrderConfirmDialogFragment: DialogFragment() {

    // ダイアログの生成処理
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

```

#### Android X
`Android`アプリ開発で利用する`SDK`の`サポートライブラリ`(=機能拡張を目的に追加されたクラス群)を、`Google`社がまとめた`ライブラリ`。
なお、`Android Studio`は、`Android X`ライブラリを標準で利用している。

> `API 28`以降は基本的に`Android X`ライブラリを継承元にする。

### ビルダーの生成
[参考: AlartDialog.Builder](https://developer.android.com/reference/kotlin/android/app/AlertDialog.Builder?hl=ja)

#### 定義

```kotlin
AlartDialog.Builder(context: Context!)
// パラメータ
// context: ダイアログを表示するアクティビティオブジェクト(=コンテキスト)
```

#### サンプルコード

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
// ダイアログを生成するクラス(DialogFragment()を継承)
class OrderConfirmDialogFragment: DialogFragment() {

    // ダイアログの生成処理
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        // Dialogオブジェクトの定義
        // activity: OrderConfirmDialogFragment()オブジェクトを呼び出すNullableなアクティビティ
        // ?: セーフコール演算子(nullでない場合に代入する値を"?.let {...}"のブロック{}末尾に記述)
        // <- ※ブロック{}内では、nullチェック対象は"it"で置換される
        //    また、nullチェック対象がnullである場合は代入されない
        val dialog = activity?.let {

            // AlertDialog.Builderオブジェクトの生成
            // AlertDialog.Builder(context: Context!)
            // context: ダイアログを表示するアクティビティオブジェクト(=コンテキスト)
            // it: let関数ブロック内における、nullチェック対象(=activity)の変数
            val builder = AlertDialog.Builder(it)
            ...
        }
        ...
    }
}
```

#### ビルダー(Builder)
複数の`プロパティ`をもつクラスの`オブジェクト`を、プロパティをコンストラクタで指定せずに生成する`デザインパターン`。

### ダイアログの表示設定

#### タイトル

```kotlin
// R値を指定して定義する場合
AlartDialog.Builder(Context!).setTitle(titleId: Int)
// 文字列を指定して定義する場合
AlartDialog.Builder(Context!).setTitle(title: CharSequence!)
```

#### コンテンツ

```kotlin
// R値を指定して定義する場合
AlartDialog.Builder(Context!).setMessage(messageId: Int)
// 文字列を指定して定義する場合
AlartDialog.Builder(Context!).setMessage(message: CharSequence!)
```

#### アクションボタン

```kotlin
// Positive Button
// R値を指定して定義する場合
AlartDialog.Builder(Context!).setPositiveButton(
    textId: Int,
    listener: DialogInterface.OnClickListener!
)
// 文字列を指定して定義する場合
AlartDialog.Builder(Context!).setPositiveButton(
    text: CharSequence!,
    listener: DialogInterface.OnClickListener!
)

// Negative Button(Positive Button同様、文字列の指定も可能)
// R値を指定して定義する場合
AlartDialog.Builder(Context!).setNegativeButton(
    textId: Int,
    listener: DialogInterface.OnClickListener!
)

// Neutral Button(同上)
// R値を指定して定義する場合
AlartDialog.Builder(Context!).setNeutralButton(
    textId: Int,
    listener: DialogInterface.OnClickListener!
)

// パラメータ
// textId, text: ボタンに表示する文字列
// listener: "タップ"イベントを検知するリスナクラスのインスタンス
```

#### サンプルコード

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
// R値を指定してダイアログのタイトルを定義
builder.setTitle(R.string.dialog_title)
// R値を指定してダイアログのコンテンツを定義
builder.setMessage(R.string.dialog_msg)

// R値を指定してダイアログのアクションボタンを定義
// Positive Button
builder.setPositiveButton(
    R.string.dialog_btn_ok,
    DialogButtonClickListener()
)
// Negative Button
builder.setNegativeButton(
    R.string.dialog_btn_ng,
    DialogButtonClickListener()
)
// Neutral Button
builder.setNeutralButton(
    R.string.dialog_btn_nu,
    DialogButtonClickListener()
)
```

### ダイアログオブジェクトの生成

#### サンプルコード

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
class OrderConfirmDialogFragment: DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = activity?.let {
            val builder = AlertDialog.Builder(it)
            ...
            // Dialogオブジェクトの生成
            // -> 返却型はAlertDialog型
            builder.create()
        }

        // 生成したDialogオブジェクトを返却
        // ?:: エルビス演算子(nullである場合の処理を":"以降に記述)
        // <- let関数ブロック{}はactivityがnullでない場合の処理であり、
        //    activityがnullである場合はdialogもnullになる
        return dialog ?: throw IllegalStateException("Activity is null.")
    }
}
```

### ダイアログ(Dialog)のリスナクラスの定義
[参考: DialogInterface](https://developer.android.com/reference/android/content/DialogInterface)

#### サンプルコード

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
class OrderConfirmDialogFragment: DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        ...
    }

    // DialogのAction Buttonの"タップ"イベントを検知するリスナクラス(リスナ)
    // DialogInterface: 各イベントのリスナインタフェースを子にするインタフェース
    // OnClickListener: "タップ"イベントを検知するメンバインタフェース
    private inner class DialogButtonClickListener: DialogInterface.OnClickListener {

        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onClick(dialog: DialogInterface?, which: Int) {
            ...
        }
    }
}
```

### アクションボタンに応じたイベントハンドラ

#### 定義

```kotlin
DialogInterface?.OnClickListener.onClick(
    dialog: DialogInterface!,
    which: Int
): Unit
// パラメータ
// dialog: "タップ"を受け取るダイアログ
// which: "タップ"されるボタン
```

##### アクションボタン(=DialogInterface)の定数値

|ボタン|定数|
|:--|:--|
|`Positive`|`DialogInterface.BUTTON_POSITIVE`<br>値: `-1`|
|`Negative`|`DialogInterface.BUTTON_NEGATIVE`<br>値: `-2`|
|`Neutral`|`DialogInterface.BUTTON_NEUTRAL`<br>値: `-3`|

#### サンプルコード

```kotlin:java/com.example.listview2/OrderConfirmDialogFragment.kt
override fun onClick(dialog: DialogInterface?, which: Int) {
    // "タップ"されたボタンに応じた分岐処理
    when(which) {
        // Positive Button
        DialogInterface.BUTTON_POSITIVE ->
            ...
        // Negative Button
        DialogInterface.BUTTON_NEGATIVE ->
            ...
        // Neutral Button
        DialogInterface.BUTTON_NEUTRAL ->
            ...
    }
    ...
}
```

## ダイアログの表示

### 定義

```kotlin:MainActivity.kt
open fun show(
    @NonNull manager: FragmentManager,
    @Nullable tag: String?
): Unit
// パラメータ
// manager: FragmentManagerオブジェクト(=supportFragmentManagerプロパティ)
// tag: ダイアログを識別するための文字列タグ(任意)
```

### サンプルコード

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
    }
    ...
    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {

        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            // ダイアログ生成クラスのインスタンス化(実体化)
            val dialogFragment = OrderConfirmDialogFragment()

            // 生成したダイアログの表示
            dialogFragment.show(supportFragmentManager, "OrderConfirmDialogFragment")
        }
    }
}
```