【Kotlin研修6日目】フラグメントを用いたマルチデバイス対応、フラグメント間の値渡し

# フラグメントを用いたマルチデバイスへの対応
`フラグメント`を用いてアプリを`マルチデバイス対応`にする手順は、以下の通り。

> 1. `res/layout/activity_main`フォルダに、<br>画面サイズに応じた`レイアウトファイル`(=`Android Resource File`)を作成<br>※`Android Resource File`生成時に自動的に配置される
> 2. 1.で作成した`レイアウトファイル`に`XML`情報を記述
> 3. 最初に表示される`フラグメント`ファイルに、<br>**画面サイズの判定**を行う変数を定義
> 4. 3.で定義した変数の値に応じた分岐処理を、<br>各`フラグメント`ファイルに記述

## layoutの設定修飾子
[参考1: アプリリリースの概要(表2.)](https://developer.android.com/guide/topics/resources/providing-resources?hl=ja)
[参考2: 各種の画面サイズのサポート(図4.)](https://developer.android.com/training/multiscreen/screensizes)
`layout-<設定修飾子>`の順番で命名する。
`設定修飾子`が複数ある場合、以下の表の上から順に`-(ハイフン)`で区切る。

|分類|設定修飾子|内容|
|:--|:--|:--|
|画面サイズ|`small`|低密度`QVGA`<br>`320x426[dp]~`|
|画面サイズ|`normal`|中密度`HVGA`<br>`320x470[dp]~`|
|画面サイズ|`large`|中密度`VGA`<br>`480x640[dp]~`|
|画面サイズ|`xlarge`|中密度`HVGA`以上<br>`720x960[dp]~`|
|画面アスペクト|`long`|`W`の付く幅が長い画面|
|画面アスペクト|`long`|`W`の付かない幅が長くない画面|
|画面の向き|`port`|`垂直`方向|
|画面の向き|`land`|`水平`方向|

## 画面サイズに応じた分岐処理

### メインフラグメント(必ず表示)

```kotlin:MenuListFragment.kt(Main)
class MenuListFragment: Fragment() {
// 10-inchの画面かどうか判定するフラグ
    private var _isLayoutXLarge = true
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        ...
        // アクティビティ内のFrameLayout(View)を取得
        val menuThanksFrame = activity?.findViewById<View>(R.id.menuThanksFrame)

        // 指定したView(FrameLayout)が同一アクティビティ内に存在しない場合
        // -> 10-inchでない場合
        if (menuThanksFrame == null) {
            _isLayoutXLarge = false
        }
    }

    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            // 10-inchの画面である場合
            if (_isLayoutXLarge) {
                ...   // フラグメントトランザクション(後述)
            }
            // 10-inchの画面でない場合
            else {
                ...   // 画面遷移
            }
        }
    }
}
```

### サブフラグメント(大画面の場合は右半分に表示)

```kotlin:MenuThanksFragment.kt(Sub)
class MenuThanksFragment: Fragment() {
    // 10-inchの画面かどうか判定するフラグ
    private var _isLayoutXLarger = true

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 10-inchの判定を行うためのフラグメント
        // fragmentManager: FragmentManagerオブジェクトを表すプロパティ
        // FragmentManager: アクティビティが保有するフラグメントを管理するクラス
        // FragmentManager.findFragmentById(Id:): 指定したID(R値)に一致するフラグメントの取得
        // id: レイアウトファイル(XML)で記述したフラグメントのID(R値)
        val menuListFragment = fragmentManager?.findFragmentById(R.id.fragmentMenuList)

        // 指定したフラグメントが同一アクティビティ内に存在しない場合
        // -> 10-inchでない場合
        if (menuListFragment == null) {
            _isLayoutXLarger = false
        }
    }

    // フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
    override fun onCreateView(inflater: LayoutInflater,　container: ViewGroup?,
        savedInstanceState: Bundle?): View? {

        // 10-inchの画面である場合(=同一アクティビティ内)
        if (_isLayoutXLarger) {
            ...   // Bundleオブジェクト(argumentsプロパティ)を通じてデータ取得
        }
        // 10-inchの画面でない場合(=別のアクティビティ)
        else {
            ...   // Intentオブジェクトを通じてデータ取得
        }
}
```

### FragmentManager
[参考: フラグメントマネージャー](https://developer.android.com/guide/fragments/fragmentmanager?hl=ja)
`アクティビティ`が保有する`フラグメント`を管理するクラス。

***

# フラグメント間の画面遷移とフラグメントトランザクション
`画面遷移`を実装する場合は、`Intent`オブジェクトを利用する。
`フラグメントトランザクション`を実装する場合は、`FragmentManager`と`FragmentTransaction`(後述)を利用する。

## 画面遷移
[参考: 研修3日目](https://qiita.com/b150005/items/f2c1d8236ac5e9770f4f)
`Fragment`クラスは、`Intent`が継承する`Activity`クラスを継承していないため、
`Activity`(を継承する)オブジェクトを利用する際は、
`FragmentActivity`クラスの`activity`プロパティ(`Nullable`型)を用いる。

### サンプルコード

```kotlin:MenuListFragment.kt(遷移元)
class MenuListFragment : Fragment() {
    ...
    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {

        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(
            parent: AdapterView<*>?, 
            view: View?, 
            position: Int, 
            id: Long
        ) {
            // 遷移元と遷移先を紐づけるIntentオブジェクトの生成
            // -> フラグメントのアクティビティオブジェクトは`activity`プロパティで表される
            val intent2MenuThanks = Intent(activity, MenuThanksActivity::class.java)

            // 遷移先アクティビティの起動
            startActivity(intent2MenuThanks)
        }
    }
}
```

```kotlin:MenuThanksFragment.kt(遷移先)
class MenuThanksFragment : Fragment() {
    ...
    // "タップ"イベントを検知するするリスナクラス(リスナ)
    private inner class ButtonClickListener: View.OnClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onClick(view: View?) {

            // アクティビティの終了
            // -> activityプロパティを中継してfinish()メソッドを利用
            activity?.finish()
        }
    }
}
```

## フラグメントトランザクション
[参考: フラグメントトランザクション](https://developer.android.com/guide/fragments/transactions?hl=ja)
`FragmentManager`によって管理される`フラグメント`の`追加`・`削除`など、
`フラグメント`に関する複数の処理を**グループ化**した`1つの処理`。

### FragmentTransactionクラス
`フラグメントトランザクション`を**制御**するクラス。

### FragmentTransactionオブジェクトの取得
`フラグメントトランザクション`を制御するのは`FragmentTransaction`クラスであるが、
`フラグメント`を管理するのは`FragmentManager`であるため、
`FragmentManager`にあたる`Fragment`クラスの`fragmentManager`プロパティを利用する。

#### 定義

```kotlin:
FragmentManager?.beginTransaction()
```

### 編集するフラグメントの定義

```kotlin
// 新規追加する場合
val newFragment = newFragment()

// 他のフラグメントを指定する場合
val editedFragment = FragmentManager?.findFragmentById(R.id.fragmentMenuList)

// 自身のフラグメントを指定する場合
// -> 記述不要
```

### フラグメントトランザクションの編集

#### フラグメントの追加

```kotlin
FragmentTransaction?.add(
    containerViewId: Int,
    fragment: Fragment!
): FragmentTransaction!
// パラメータ
// containerViewId: 追加先のレイアウト部品のID(R値)
// fragment: 追加するフラグメントオブジェクト
```

#### フラグメントの置換

```kotlin
FragmentTransaction?.replace(
    containerViewId: Int,
    fragment: Fragment!
): FragmentTransaction!
// パラメータ
// containerViewId: 置換先のレイアウト部品のID(R値)
// fragment: 置換するフラグメントオブジェクト
```

#### フラグメントの削除

```kotlin
FragmentTransaction?.remove(
    fragment: Fragment!
): FragmentTransaction!
// パラメータ
// fragment: 削除するフラグメントオブジェクト
```

### 編集したフラグメントトランザクションの反映(コミット)

#### 定義

```kotlin
FragmentTransaction?.commit()
```

### サンプルコード

#### フラグメントの新規追加

```kotlin:MenuListFragment.kt
class MenuListFragment : Fragment() {
    ...
    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {

            // フラグメントトランザクションの編集開始
            val transaction = fragmentManager?.beginTransaction()

            // 追加するフラグメントオブジェクトの生成
            val menuThanksFragment = MenuThanksFragment()

            // フラグメントの置換
            transaction?.replace(R.id.menuThanksFrame, menuThanksFragment)

            // 編集したフラグメントトランザクションを反映(コミット)
            transaction?.commit()
        }
    }
}
```

#### 追加したフラグメントの削除

```kotlin:MenuThanksFragment.kt
class MenuThanksFragment : Fragment() {
    ...
    // "タップ"イベントを検知するするリスナクラス(リスナ)
    private inner class ButtonClickListener: View.OnClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onClick(view: View?) {

            // フラグメントトランザクションの編集開始
            val transaction = fragmentManager?.beginTransaction()

            // フラグメントの削除
            transaction?.remove(this@MenuThanksFragment)

            // 編集したフラグメントトランザクションを反映(コミット)
            transaction?.commit()
        }
    }
}
```

***

# フラグメントトランザクションにおけるフラグメント間の値渡し
追加する`フラグメント`の`Bundle`型である`arguments`プロパティを利用して値を渡す。
`遷移元`フラグメントでは、`Intent`オブジェクトがもつ`Bundle`オブジェクトに直接`データ`を格納する。
`遷移先`フラグメントでは、`Bundle`オブジェクトのデータを、
`Fragment`クラスの`argument`プロパティを用いてデータを取得する。

## Bundle

[参考: Bundle](https://qiita.com/kojionilk/items/138eea19dadb14997136)
`キー`と`値`を格納した`Map<String, Any>`型のデータ構造をもつ`コレクション`。
このデータ構造を用いて、各種`データ`に名前を付けて管理することができる。

## Bundleを用いたインテントへのデータ格納

### サンプルコード

```kotlin:MenuListFragment.kt(追加元)
class MenuListFragment: Fragment() {
    ...
    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {

        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            ...
            // Bundleオブジェクトの生成
            val bundle = Bundle()

            // Bundleオブジェクトへのデータ格納
            bundle.putString("menuName", menuName)
            bundle.putString("menuPrice", menuPrice)
            ...
            // 遷移先フラグメントオブジェクトの生成
            val menuThanksFragment = MenuThanksFragment()

            // 遷移先オブジェクトのargumentsプロパティにBundleオブジェクトを代入
            // -> 遷移先ではargumentsプロパティを用いてデータを取得
            menuThanksFragment.arguments = bundle
        }
    }
}
```

## Bundleを用いたインテントからのデータ取得

### サンプルコード

```kotlin:MenuThanksFragment.kt(追加先)
class MenuThanksFragment: Fragment() {
    // フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
        // データが格納されるBundleオブジェクト
        // -> 画面サイズに応じた分岐処理を行えるよう初期化せず定義
        val extras: Bundle?

        // Bundleオブジェクトにデータを格納
        // arguments: データが格納されたBundleオブジェクト
        // <- 追加元でargumentsプロパティにデータを格納済み
        extras = arguments

        // キーを指定して値を取得
        val menuName = extras?.getString("menuName")
        val menuPrice = extras?.getString("menuPrice")
        ...
}
```

# 画面遷移におけるフラグメント間の値渡し
[参考: 研修3日目](https://qiita.com/b150005/items/f2c1d8236ac5e9770f4f)
`Intent`オブジェクトを利用して値を渡す。

> `Bundle`は`Intent`クラスを、`Intent`は`Activity`クラスを継承しているものの、
`Fragment`は`Activity`クラスを継承していないため、
`フラグメント`でこれらのオブジェクトを利用する場合は、
`フラグメント`の`activity`プロパティ(`Nullable`型)を利用する。

## サンプルコード

```kotlin:MenuListFragment.kt(遷移元)
class MenuListFragment : Fragment() {
    ...
    // ListViewのItemの"タップ"イベントを検知するリスナクラス(リスナ)
    private inner class ListItemClickListener: AdapterView.OnItemClickListener {
        // "タップ"イベント検知時の処理(イベントハンドラ)
        override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {

            // Bundleオブジェクトの生成
            val bundle = Bundle()

            // Bundleオブジェクトへのデータ格納
            bundle.putString("menuName", menuName)
            bundle.putString("menuPrice", menuPrice)

            // 画面遷移を実現するIntentオブジェクトの生成
            // -> FragmentクラスはActivityクラスを継承していないため、 Fragment.activityプロパティを指定
            val intent2MenuThanks = Intent(activity, MenuThanksActivity::class.java)

            // Bundleオブジェクトへのデータの格納
            intent2MenuThanks.putExtras(bundle)
            ...
        }
    }
}
```

```kotlin:MenuThanksFragment.kt(遷移先)
class MenuThanksFragment: Fragment() {
    // フラグメントのレイアウト(XML)のインフレート(フラグメントの生成)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ...
        // データが格納されるBundleオブジェクト
        // -> 画面サイズに応じた分岐処理を行えるよう初期化せず定義
        val extras: Bundle?

        // 遷移元と遷移先を紐づけるIntentオブジェクト
        // -> FragmentクラスはActivityクラスを継承していないため、
        //    activityプロパティ(Nullable型)を中継してintentプロパティを利用
        // intent: Intentオブジェクト(Activityクラスのプロパティ)
        val intent = activity?.intent

        // データが格納されたBundleオブジェクト
        // extras: Intentオブジェクトに格納されたキーと値を格納するBundleオブジェクト
        extras = intent?.extras
    }
}
```