Andorid DataBinding with Kotlin

実用的なライブラリをKotlin交えて紹介しようプロジェクト１

昨今のAndroidプロジェクトでは必須でもある
DataBindingについて、使い方をあらっていこうと思います。

# What is Data Binding?

[Googleが提供している公式ライブラリ](https://developer.android.com/topic/libraries/data-binding/index.html)で
レイアウトにデータを流すことができる便利なライブラリです

Android標準の方法ですと
ソース上でViewを取得し、それに対してデータを設定する必要がありますが
DataBindingを使うことでもっと簡単にレイアウト上のデータを反映することができます。

## Example Code

たとえば、Viewに対しての操作をActivity上でこう書いていたとします。

```Kotlin
val titleView = findViewById(R.id.title) as TextView
titleView.setText(data.title)
titleView.setTextColor(data.color)
val subTitleView = findViewById(R.id.subTitle) as TextView
subTitleView.setText(data.subTitle)
subTitleView.setTextColor(data.color)

 : (省略)

val progressBar = findViewById(R.id.progress) as ProgressBar
progressBar.setVisibility(if (data.loading) View.VISIBLE else View.INVISIBLE)
val titleTextView = findViewById(R.id.title) as TextView
titleTextView.setVisibility(if (data.loading) View.INVISIBLE else View.VISIBLE)
val subTitleView = findViewById(R.id.subTitle) as TextView
subTitleView.setVisibility(if (data.loading) View.INVISIBLE else View.VISIBLE)
```

うっとおしいですよねこれ。
ぼくも大嫌いでした。

しかしこれが、ソース上、こんなにスッキリ書けます。

```Kotlin
val binding = DataBindingUtil.setContentView(R.layout.activity_main)
binding.rowData = data
```

そして代わりに、どんなときにどんな値をもつかを
レイアウト上に記述することが可能になります。

```xml
<!-- For title -->
<TextView
	...
	android:textColor="@{data.color}"
	android:text="@{data.title}" />

<!-- For subtitle -->
<TextView
	...
	android:textColor="@{data.color}"
	android:text="@{data.subtitle}" />

<!-- For loading progress -->
<ProgressBar
	...
	android:visibility="@{data.loading ? View.VISIBLE : View.INVISIBLE}" />
```

# メリット

あくまで個人の意見です

## ViewとデータModelを簡単に分けられる

レイアウトファイルに何のデータを表示するかを記載し、
ソースにはレイアウトにデータを流すコードだけを記載するのが基本です。
そのため、実装をしていると自然とViewとデータModelクラスが分離していきます。

こうするとそれぞれがシンプルな形となり、
テストからメンテナンスまでしやすくなります。
そう考えると、非常に導入するメリットが大きいかと思います。

昨今よく聞くMVVMとの融和性が非常に高いです。
Kotlinを使うと、各レイヤー間のやりとりも高階関数で補えるのがまたGoodです。

![1-VLhXURHL9rGlxNYe9ydqVg 2.png](https://qiita-image-store.s3.amazonaws.com/0/85214/5ea23f56-6ae9-9557-787b-8fb40578df52.png "1-VLhXURHL9rGlxNYe9ydqVg 2.png")


[参照サイト](https://labs.ribot.co.uk/approaching-android-with-mvvm-8ceec02d5442)


## View取得が楽

DataBindingを使うと、Viewのキャストミスもなくなりますし、
コードも見やすくなります。

Android標準では、findViewById を使って取得できる
Viewインスタンスをキャストする必要があります。

```Kotlin
val titleTextView = findViewById(R.id.title) as TextView
titleTextView.setText(data.title)
```

しかし、DataBindingだと簡単にキャストされた形でViewを取得できます

```Kotlin
// binding.title が、id=titleのTextViewを指す
val binding = DataBindingUtil.setContentView(R.layout.activity_main)
binding.title.setText(data.title)
```

## なんたって公式

昔はButterKnifeというライブラリが
DataBindingのような機能を提供していました。
広く使われていたとも聞きます。

しかし２年ほど前からGoogleが公式サポートしましたので、
どんどんこちらへ移行しているのが実情のようです。

やっぱり公式だと安心ですしね。

# デメリット

これといって思いつかなかったんですが、
強いて言うなら...

## エラーが見づらくなる

DataBindingのライブラリは、自動生成されるクラスを介して利用します。

しかし、コンパイルエラーが発生すると、これらのUtilクラスがなくなってしまうため、
見たいエラーがDataBinding用のクラス参照エラーで埋もれてしまいます。

だから注意してコーディングしなはれや！それだけ。

## 慣れるまで見づらい

処理をレイアウトとコードどちらにも書くため、
見るソースが単純に増えます。

そのため、とくに初心者の方からすると、
最初は見づらく感じるかもしれません。

しかし、アプリ（プロジェクト）が大きくなるにつれ
そのありがたみもわかりますし、そのうち解消されるでしょう。

# 基本
ではいよいよ使ってみましょう。
いくつか手順があります。

1. gradleファイルに宣言
2. レイアウトに `<layout>` タグをつける
3. コード上でBindingクラスを取得
4. Bindingインスタンスを使って操作

## gradleファイルに宣言
アプリのGradleファイルに、以下を加えてSyncしましょう

```gradle
apply plugin: 'kotlin-kapt'

android {
	...(省略)...
	dataBinding {
    	enabled = true
	}
}

dependencies {
	kapt "com.android.databinding:compiler:2.3.3" //数値はGradleBuildバージョン
}
```

これで準備OKです

## レイアウト宣言

レイアウトファイルの中を`<layout>`タグでくくることで
DataBindingを利用する対象のレイアウトという宣言ができます。

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <Button 
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content" />
    </LinearLayout>
</layout>
```

こんな感じ

## Bindingインスタンス取得

では、実際にこのレイアウトにアクセスするための
Bindingインスタンスを取得します

```Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // with setContentView
    val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
}
```

DataBindingUtil ... DataBindingのライブラリがもともともつUtilityクラス
ActivityMainBindng ... レイアウトファイルを`<layout>`タグで囲むことで自動生成されるクラス。レイアウトファイル名＋Bindingになる。

Bindingインスタンスを取得する方法はいくつかあるんですが、
inflateするだけなどの場合はこう書きます

```Kotlin
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    val binding = convertView
        ?.let { DataBindingUtil.findBinding(convertView) } // ViewからBindingインスタンス取得
        ?: RowListBinding.inflate(mLayoutInflater) // inflateするだけ
    return binding.root
}
```

## id参照

例えばこのようにレイアウトファイルを生成し、
Buttonにidを設けたとします

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <Button 
        		android:id="@+id/button"
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content" />
    </LinearLayout>
</layout>
```

するとBindingインスタンスを介してこのようにアクセスできます

```Kotlin
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.button.text = "Button Text"
```

このときの`binding.button`は参照時点ですでにButtonクラスにキャストされています。

ちなみに、本当にソース責務を分離するのであれば、
レイアウトにidを入れることがなくなるそうです。
（id入れるってことは、別のレイヤーソースで使うってことでしょ的な考え）

## データ挿入
では実際にレイアウトにデータを流す方法ですが、
方法がいくつかあるので、用途によって使い分けましょう

### Basic

#### レイアウト:データ宣言
レイアウトにdataを定義する必要があります

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="data" type="com.android.example.Data" />
    </data>
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <Button 
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content"
        		android:text="@{data.title}" />
    </LinearLayout>
</layout>
```

`<data>` がいわゆるデータを宣言するエリア
`<variable>` がデータの宣言、
その中でも`name`はレイアウトファイル内での変数名のようなもの、
`type`はクラスの宣言になります。（もちろんインターフェースでも問題ありません）

#### レイアウト:データ反映宣言

ではどこに何を反映させるか、という書き方ですが
レイアウトファイルの `@{}` で囲まれた箇所がそれにあたります。

この場合、ButtonのテキストにDataクラスのもつ `title` という要素を入れる、という表現になります
もっと言えば、ButtonのSetterメソッドに渡す値を `@{}` で指定します

#### データ挿入
対応するレイアウトBindingクラスに set＋VariableのName メソッドができるので
それにデータを指定することでデータをレイアウトに流すことができます

```Kotlin
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val data = Data()
binding.data = data // Javaだとbinding.setData(data);
```

すると、このタイミングでレイアウト上にtitle がButtonに反映されます

## データ監視

挿入したデータを、動的に変化させたい！
というときに利用します。

### Observable（監視）
双方向のデータ通信方法その１
たとえばEditTextの入力内容を監視するときに使います。
#### レイアウト

```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="data" type="com.android.example.Data" />
    </data>
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <Button 
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content"
        		android:text="@{data.title}" />
    </LinearLayout>
</layout>
```

#### コード

データModelクラスはこのように書きます

```Kotlin
data class Data(val title: ObservableField<String>) {
    init {
        title.set("")
    }
}
```

ObservableField は型が指定できますが、少し重いらしいです。

ObservableBoolean、
ObservableByte、
ObservableChar、
ObservableShort、
ObservableInt、
ObservableLong、
ObservableFloat、
ObservableDouble、
ObservableParcelable 
といった各種変数のObservableクラスが用意されているので、
可能な限り、そちらを利用しましょう。

### Bindable（監視）
双方向のデータ通信方法その２
その１で実装できないパターンはこちらを使います。
たとえばEditTextの入力内容が変わったときに何かしたい！というときに使えます。

**なぜか動く時と動かないときがあります。。調査中**

#### レイアウト

```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="data" type="com.android.example.Data" />
    </data>
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <EditText 
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content"
        		android:text="@={data.title}" />
    </LinearLayout>
</layout>
```

#### コード

- BaseObservableを継承する
- 監視したいGetter/Setterに@Bindableをつける

```Kotlin
class Data: BaseObservable() {

    var title: String = ""
        @Bindable get() = field
        @Bindable set(value) {
            field = value 
            notifyPropertyChanged(BR.title)
            
            // ＋アルファ
        }
}
```


# 応用（＋実例）
## イベント処理
### リスナーBinding

onClickなどのリスナーで、ラムダ式のように記述をして
直接メソッドコールさせることが可能です。

レイアウトにリスナーを書くようなイメージでしょうか。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout
    		android:layout_width="match_parent" 
    		android:layout_height="match_parent">
        <Button 
        		android:layout_width="wrap_content" 
        		android:layout_height="wrap_content"
        		android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>
```
### メソッド参照

`ClassName::MethodName` の形で、
メソッドの参照をさせることが可能です。

```Kotlin
class MyHandlers {
    fun onClickFriend(view: View) { ... }
}
```

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>
```

### 注意
書けるのはいいんですが、
複雑なものをこうして書くと返って読みづらくなります。

ほどほどにしておきましょう！

## レイアウト詳細
### import

フレームワークにある定数などを利用する場合、
レイアウトで利用するクラスとしてimportすることができます

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

    <data>
        <import type="android.view.View" />
        <variable
            name="task"
            type="com.example.Task" />
    </data>
    ...
    <ProgressBar
    	...
    	android:visibility="@{task.loading ? View.VISIBLE : View.INVISIBLE}" />
```

例えばListのimportなどもこのように行います

```
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <import type="com.example.model.Person" />
        <import type="java.util.List"/>
        <variable name="person" type="Person"/>
        <variable name="persons" type="List&lt;Person&gt;"/>
    </data>
```

ただ注意点として、現在のDataBindingライブラリだと
ビルドは通りますが、IDEのエディタでの表示上、エラーがでてしまいます。

### Bindingクラス名の変更
あんまり使わないですが、自分で決めた名前でBindingクラスを作ることが可能です。

```
<data class="ContactItem">
    ...
</data>
```

### include

レイアウトファイルの中で、他レイアウトを`<include>`してる方もいると思います。
その場合でも心配無用、きちんとデータを渡す方法があります。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>
```

`bind:xxxx` としたxxxx部分が、
include先のdataのname定義になります。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

```

### 式言語

以下は普通に`@{}`の中で利用することが可能です
簡単な条件分岐や計算であれば、書いた方がいいでしょう

- 算術演算子 + - / * %
- 文字列連結演算子 +
- 論理演算子 && ||
- バイナリビット演算子 & | ^
- 単項演算子 + - ! ~
- シフト演算子 >> >>> <<
- 比較演算子 == > < >= <=
- instanceof
- グルーピング ()
- リテラル: 文字、文字列、数字、null
- キャスト
- メソッド呼び出し
- フィールド アクセス
- 配列アクセス []
- 三項演算子 ?:

### null合体演算子

NULLケースを踏まえたケースを`??`を使うことで表現できます

```xml
<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{data.name ?? @string/no_name}"
    tools:text="Title"/>
```

### リソース参照

先出ししてしまいましたが、@stringなどのリソース参照も普通にできます。
引数を利用する場合においても活用可能です。

```xml
<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{data.valid ? @string/name_with_tag(data.name) : @string/no_name }"
    tools:text="Title"/>
```

```xml
<string name="no_name">NO NAME</string>
<string name="name_with_tag">NAME: %s</string> <!-- 引数がひとつのケース -->
<string name="name_with_tag2">NAME: %1s, %2s</string> <!-- 引数がひとつのケース -->
```

もちろん、@colorや@drawableなども可能です。
場合によって色分けやアイコンを変える時などは便利ですね。

## Bindingの同期
DataBindingは基本的にバックグラウンドで
適当なタイミングで実行されます。

executePendingBindings()を利用すると、
その場でbindingの同期をとってくれます

## BindingAdapter
BindingAdapterというアノテーションが用意されており、
これを利用することでレイアウトに対するSetterを宣言することができます。

たとえばAndroidの android:paddingLeft は以下のように定義されています

```Java
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}
```

こいつらは以下を守っていれば、どこに定義してあげてもいいです。
（Utilクラスを作って定義することが一般的かとは思いますが。。）
- BindingAdapterアノテーションを定義している
- public static なメソッドである
- 第一引数は対象のViewクラス、第二引数以降はBindingAdapterの定義順

たとえば複数の引数をとりたい場合はこのように書きます

```Java
@BindingAdapter({"listData", "onItemClick"})
public static void setCustomAdapter(ListView listView, List<String> listData, OnItemClickListener listener) {
    listView.setAdapter(new CustomAdapter(listData, listener));
}
```

```xml
<ListView
    android:width="match_parent"
    android:height="match_parent"
    app:listData="@{data.listData}"
    app:onItemClick="@{activity}"
```

Kotlinの場合はExtensionで定義させることも可能です。

```Kotlin
@BindingAdapter("data")
fun ListView.loadAdapter(data: List<Person>) {
    BMIListAdapter(this.context, data).also {
        this.adapter = it
        this.onItemClickListener = it
    }
}
```

余談ですが、私の担当しているプロジェクトではKotlinで書けません。
導入しているライブラリが影響しているのか、Javaと混ぜて書いてるかはわかりませんが。。

Kotlinだけで書いてるプロジェクトの方は問題なく使えるはずです！

## DataBindingのsafeUnbox()の使い所

`app:answer="@{visitReport.answer != null ? visitReport.answer : 0}"`
こういう定義をしていて、answerがIntegerの場合、
JavaコードだとこれでOKだけど、DataBinding使ってレイアウト上でこれ記述すると、
たぶんコンパイル方法の関係で `? visitReport.answer` の箇所で
「NPE発生の可能性あり」と警告がでてしまう。

そんなときのために DataBindingではUtilityクラスが用意されている模様。
なのでデフォルトが0のときなどはこれ使えばOKみたい。

```
package android.databinding;
public class DynamicUtil {
    public static int safeUnbox(java.lang.Integer boxed) {
        return boxed == null ? 0 : (int)boxed;
    }
    public static long safeUnbox(java.lang.Long boxed) {
        return boxed == null ? 0L : (long)boxed;
    }
    public static short safeUnbox(java.lang.Short boxed) {
        return boxed == null ? 0 : (short)boxed;
    }
    public static byte safeUnbox(java.lang.Byte boxed) {
        return boxed == null ? 0 : (byte)boxed;
    }
    public static char safeUnbox(java.lang.Character boxed) {
        return boxed == null ? '\u0000' : (char)boxed;
    }
    public static double safeUnbox(java.lang.Double boxed) {
        return boxed == null ? 0.0 : (double)boxed;
    }
    public static float safeUnbox(java.lang.Float boxed) {
        return boxed == null ? 0f : (float)boxed;
    }
    public static boolean safeUnbox(java.lang.Boolean boxed) {
        return boxed == null ? false : (boolean)boxed;
    }
}
```