Data Bindingのセットアップ&使い方(Kotlin)

## はじめに

Data Bindingのセットアップ方法と使い方を紹介します。

## 「Data Binding」とは？

Googleが提供しているJetpackのコンポーネントです。
UIコンポーネントとデータソースを、コードでなく宣言的にバインドできるようになります。

## 環境

- OS：macOS Big Sur 11.5.2
- Android Studio：Arctic Fox | 2020.3.1 Patch 1
- Kotlin：1.5.21
- Gradle：7.0.2
- Gradle plugin：7.0.1

## セットアップ

### Data Bindingの有効化

appフォルダ配下の「build.gradle」でData Bindingを有効化します。

```diff_groovy:/app/build.gradle
android {
+   buildFeatures {
+       dataBinding true
+   }
}
```

これでData Bindingのセットアップは完了です。

## 使い方

### フラグメントでData Bindingする

以下の記事で説明したフラグメントを、View BindingからData Bindingに変更します。

https://qiita.com/uhooi/items/56cbf16cc28fe5e65b47

#### レイアウトファイルの編集

Data Bindingするには、レイアウトファイルのルートタグを `layout` に変更する必要があります。
忘れがちなので注意しましょう。

```xml:monster_detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.monsterdetail.MonsterDetailFragment">

        <ImageView
            android:id="@+id/icon_imageview"
            android:layout_width="240dp"
            android:layout_height="240dp"
            android:layout_marginTop="56dp"
            android:contentDescription="@string/icon_description"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0"
            app:layout_constraintVertical_chainStyle="packed"
            tools:src="@tools:sample/avatars" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
```

ルートタグを変更したら、バインドしたいプロパティを `data` タグに追加します。
今回は `monster` プロパティを対象とします。

```kotlin:MonsterDetailFragment.kt
class MonsterDetailFragment : Fragment() {
    private val monster: MonsterItem = ○○
}
```

```diff_xml:monster_detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

+   <data>
+ 
+       <variable
+           name="monster"
+           type="com.theuhooi.uhooipicbook.ui.monsterlist.entities.MonsterItem" />
+   </data>
+ 
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.monsterdetail.MonsterDetailFragment">

        <ImageView
            android:id="@+id/icon_imageview"
            android:layout_width="240dp"
            android:layout_height="240dp"
            android:layout_marginTop="56dp"
            android:contentDescription="@string/icon_description"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0"
            app:layout_constraintVertical_chainStyle="packed"
            tools:src="@tools:sample/avatars" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
```

ただ `iconImageView` にバインドしたいのは `monster.iconUrlString` であり、 `ImageView` にはURLをレイアウトファイルから紐付ける機能がありません。

#### バインディングアダプターの実装

そこでバインディングアダプターの出番です。
レイアウトファイルでバインドできるよう、アダプターを用意します。

バインディングアダプターは `util/ViewBindingAdapters.kt` のように1ファイルにまとめることが多いようです。

```kotlin:util/ViewBindingAdapters.kt
@BindingAdapter("imageUrl")
fun load(imageView: ImageView, imageUrl: String?) {
    imageView.load(imageUrl)
}
```

メソッドの第1引数に対象のビュー、第2引数に渡したい値を記述します。

`@BindingAdapter` アノテーションにはレイアウトファイルに記述する名前を付けます。
基本的には第2引数と同名でいいと思います。

#### レイアウトファイルでバインディング

アダプターを実装したら、レイアウトファイルでバインディングします。

```diff_xml:monster_detail_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="monster"
            type="com.theuhooi.uhooipicbook.ui.monsterlist.entities.MonsterItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.monsterdetail.MonsterDetailFragment">

        <ImageView
            android:id="@+id/icon_imageview"
            android:layout_width="240dp"
            android:layout_height="240dp"
            android:layout_marginTop="56dp"
            android:contentDescription="@string/icon_description"
+           app:imageUrl="@{monster.iconUrlString}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0"
            app:layout_constraintVertical_chainStyle="packed"
            tools:src="@tools:sample/avatars" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
```

レイアウト式は `@{}` で括ることで記述できます。
補完が効くので便利です。

レイアウト式では演算子やメソッドを呼び出せますが、単体テストができないため、シンプルにすることが推奨されています。
シンプルにする手段の一つとして、先ほど実装したバインディングアダプターがあります。

#### コードによるバインディング処理を削除

コードによるバインディング処理は不要になったため削除します。

```diff_kotlin:MonsterDetailFragment.kt
class MonsterDetailFragment : Fragment() {
    private var _binding: MonsterDetailFragmentBinding? = null
    private val binding get() = _binding!!

    private val monster: MonsterItem = ○○

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

-       binding.iconImageview.load(monster.iconUrlString)
    }
}
```

## おわりに

これでバインディングがレイアウトファイルのみで完結できました！
レイアウトファイルが単体テストできないことを念頭に置きつつ、有効活用していきましょう :relaxed: 

今回は紹介しませんでしたが、双方向バインディングという機能も便利です。
私は使ったことがないので、機会があれば使いたいです。

## 参考リンク

- [データ バインディング ライブラリ  |  Android デベロッパー  |  Android Developers](https://developer.android.com/topic/libraries/data-binding?hl=ja)
- [スタートガイド  |  Android デベロッパー  |  Android Developers](https://developer.android.com/topic/libraries/data-binding/start?hl=ja)
- [レイアウトとバインディング式  |  Android デベロッパー  |  Android Developers](https://developer.android.com/topic/libraries/data-binding/expressions?hl=ja)
- [バインディング アダプター  |  Android デベロッパー  |  Android Developers](https://developer.android.com/topic/libraries/data-binding/binding-adapters?hl=ja)