DataBindingのあれこれ

# はじめに

基本的なことは省略してます。

## 設定

```build.gradle
android {
    …
    dataBinding {
        enabled = true
    }
}
```

`Android Studio`および`gradle plugin`の`2.2.0`を利用している人は、[悲しいバグを踏む](https://code.google.com/p/android/issues/detail?id=223321)ので`2.2.1`にしましょう。

## 制限

現状では、**JackコンパイラでDataBindingは使えません**。
そのため、Java8の機能が使いたい場合はライブラリで補完しましょう。

| 機能 | ライブラリ |
|:----|:----|
| Lambda | [Retrolambda](https://github.com/orfjackal/retrolambda) |
| Stream API | [Lightweight-Stream-API](https://github.com/aNNiMON/Lightweight-Stream-API) |

## 制約

- Javaの予約語に準拠
- `context`は変数名として使えません
 - **Contextインスタンスが参照できます**
- マルチバイト文字は使えません
 - マルチバイト文字のメソッドなどを呼び出しているとビルドに失敗します

## DataBindingの特徴

とりあえずこのへんに触れます。

- Null安全
- メソッドアクセスの効率化
- mergeタグ不要
- DataBindingの連係
- カスタム属性


# Null安全

`DataBinding`では、`レイアウト`に書いたコードについては`NullPointerException`にならないという特性があります。
特に記述されていませんが、これには配列やListの`ArrayIndexOutOfBoundsException`なども含まれています。

```activity_main.xml
<layout>
    <data>
        <variable
            name="person"
            type="foo.bar.Person"/>
    </data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{person.name}"/>

</layout>
```

この場合、コード側で

```MainActivity.java
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setPerson(null);
```

と、`null`をセットされていても、ぬるぽにはなりません。

```
android:text="@{person.name.firstName}"
```
この場合の、`name`が`null`の場合も同様です。
配列外アクセスの場合も同様に安全に処理してくれます。（実装上は`null`であると判定される）

`DataBinding`のシンタックスシュガーとして、**`A ?? B`**というものがあります。
これは左辺Aが`null`の場合に右辺Bを使うという表現ですが、これは左辺が配列外アクセスであっても同様に適用されます。

# メソッドアクセスの効率化

`DataBinding`では**1回の画面更新**で、**同じ処理は1度しか行わない**ように実装されています。

これはどういうことかというと、

```activity_main.xml
…
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{person.getName()}"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{person.getName() + ` (` + person.getAge() + `)`}"/>

```

このように、2つの`TextView`で同じメソッド`getName()`へアクセスしている場合、生成されるコードでは以下のように使われるということです。（実際のコードとは異なります）

```ActivityMainBinding.java
String name = person.getName();
int age = person.getAge();

textView1.setText(name);
textView2.setText(name + " (" + age + ")");
```

この特性は`nullチェック`にも適用され、自分で書くとめんどくさいローカル変数出しを自動で行ってくれます。

## getterの特性

ちなみに、上では`person.getName()`と書きましたが、`DataBinding`は**publicなgetter**を変数アクセスのように記述できます。
`person.getName()`の場合`person.name`としても有効になります。（むしろ後者しか補完されません）

では、`person.getName()`と`person.name`が混在するとどうなるのか。
これは、別の処理として認識されるようで、2つの変数に分割されます。ちなみに`name`が**publicな変数**だったとしても、`person.name`は**getterを優先して呼び出されます**。


# mergeタグ不要

レイアウトXMLでは、`merge`タグというものが存在します。
これは`include`タグや`ViewGroup`に対して`attachToRoot=true`でレイアウトを`inflate`したときに、どのような階層になるかを指定するものです。（詳細は省きます）

`DataBinding`はトップ階層を`layout`タグにする必要があります。それに対して`merge`タグはトップ階層にしか定義できません。

ではどうするのか？


答えは、**`merge`タグは利用できない**です。
公式を見ても、[Data binding does not support include as a direct child of a merge element.](https://developer.android.com/topic/libraries/data-binding/index.html#includes)としか書いてありません。

この説明、非常に不親切なんですが、実際は**`merge`タグを使う必要がない**のほうが正しいです。

`layout`タグが非常に柔軟にできていて、`include`タグでレイアウトを挿入した場合、`merge`の役割を果たしてくれます。
逆にコードで`inflate`した場合、必要に応じて`FrameLayout`で囲ってくれます。つまり、エンジニアはこの動作について気にする必要がないということです。


# DataBindingの連係

`include`タグ関連でもう2つ便利な動作を紹介します。

## DataBindingからDataBindingを取得する

```activity_main.xml
<layout>
    …
    <LinearLayout>
        …
        <include
            android:id="@+id/foo_binding"
            layout="@layout/layout_foo"/>

    </LinearLayout>
</layout>
```

```layout_foo.xml
<layout>
    <data>
        <variable
            name="age"
            type="int"/>  
    </data>

    …
</layout>
```

`activity_main.xml`で、`include`タグに`id`を振ってみます。
するとコード側では、

```MainActivity.java
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

Person person = new Person("山田太郎", 25);
binding.setPerson(person);

// BindingからBindingが生える
LayoutFooBinding fooBinding = binding.fooBinding;
fooBinding.setAge(person.getAge());
```

このように、`ActivityMainBinding`から`LayoutFooBinding`にアクセスできるようになります。

`View`、`DataBinding`に限らず、とりあえずアクセスしたければ**`id`を振れば生えてくる**ということです。
逆に`id`を振らなければ隠蔽できます。

## DataBindingからDataBindingへのデータ渡し

上の例では、コードからデータを渡していましたが、じつはこのコードはレイアウトxmlで完結させることできます。

`layout_foo.xml`には、`int型`の変数`age`が定義されています。
この場合、`include`している側の`activity_main.xml`は、`include`タグの属性に以下のような記述ができます。

```activity_main.xml
<layout>
    …
    <LinearLayout>
        …
        <include
            layout="@layout/layout_foo"
            app:age="@{person.age}"/>

    </LinearLayout>
</layout>
```

`layout_foo.xml`で定義した`variable`の名前をそのまま、`app:<variableName>`として指定できます。

# カスタム属性

`data`タグに定義されている変数以外に、`DataBinding`では任意の属性を外から追加することができます。

| 定義 | 属性名 | 利用可能箇所 |
|:-----|:------|:------------|
| `variable`タグ | `app:<variableName>` | `include`タグ |
| `View`の`public setter` | `android:onClickListener`など | 対象の`View` |
| `@BindingMethod`アノテーション | 任意の名前 | 定義対象の`View` |
| `@BindingAdapter`アノテーション | 任意の名前 | 引数の`View` |

これらのうち、下3つについて簡単に説明します。

## public setterの属性化

`DataBinding`では`View`に定義されている全ての**public setter**に属性としてアクセスできます。（hideなものはわかりません）

例えば`CustomView`を作るとき、必要なデータを`setter`から受け取れるようにしたとします。

```CustomView.java
public CustomView extends View {
    …

    public void setPerson(Person person) {
        …
        invalidate();
    }
}
```

通常は、このメソッドに対してコードからデータを渡す処理を書くと思います。
しかし、`DataBinding`では、**publicなsetterは属性化される**という特性を持つため、レイアウトxmlで記述することもできます。

```activity_main.xml
<layout>
    <data>
        <variable
            name="person"
            type="foo.bar.Person"/>
    </data>

    <foo.bar.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:person="@{person}"/>

</layout>
```

こうすることで、1つのデータを複数の`View`にそれぞれセットせずに、`DataBinding`に渡せばOKという一貫した処理にできます。

ただしこの場合、**値が`null`の場合の処理はCustomViewに委ねられる**という点に注意が必要です。（実装次第でぬるぽが起こり得る）

## BindingMethod

`@BindingMethods`及び`@BindingMethod`を利用することで、**任意の属性を既存のメソッドにリンクさせる**ことができます。

例として、`View`には`android:onClick`という属性が定義されていますが、この属性名`onClick`に対応するメソッド`setOnClick()`は`View`には定義されていません。
にも関わらず、`android:onClick="@{onClickListener}"`は有効です。

これは、デフォルトで定義されている`ViewBindingAdapter`に、属性名とメソッドの対応が定義されているためです。

```ViewBindingAdapter.java

@BindingMethods({
    …
    @BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener")
})
public class ViewBindingAdapter {
    …
}
```

このように、`DataBinding`では、属性名とメソッド名が一致しないいくつかの属性について、デフォルトのリンク定義がされています。([参照](https://android.googlesource.com/platform/frameworks/data-binding/+/android-7.0.0_r29/extensions/baseAdapters/src/main/java/android/databinding/adapters))

## BindingAdapter

`@BindingMethod`は既存のメソッドへのリンクでしたが、`@BindingAdapter`は実装を定義できます。

以下の`TextView`を書いたとします。

```activity_main.xml
<layout>
    <data>
        <variable
            name="person"
            type="foo.bar.Person"/>
    </data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{person}"/>
</layout>
```

`TextView`の`android:text`に、`Person型`の変数を代入しています。
通常、`TextView`には`Person型`を受け取る`setText()`メソッドは存在していないため、これはビルドエラーとなります。

しかし、ここで`@BindingAdapter`を利用することで、この属性定義を有効にすることができます。
`@BindingAdapter`は任意のクラスに定義できます。（競合する場合は、先に見つかったほうが優先されるっぽいです）

```CustomTextViewBindingAdapter.java
public class CustomTextViewBindingAdapter {

    @BindingAdapter("android:text")
    public static void setText(TextView textView, Person person) {
        String name = person != null ? person.getName() : null;
        textView.setText(name)
    }
}
```

`@BindingAdapter`の定義ルールは、

- **public static** なメソッドであること
- 第一引数を適用対象のViewとすること
 - **サブクラスにも適用される**
- 第二引数以降とアノテーションの`value`の属性の順番を合わせること

です。
上の定義では、第一引数が`TextView`なので、`TextView`とそのサブクラスに適用されます。
第二引数は`Person型`なので、`android:text`に`Person型`の場合にこのメソッドが呼び出されます。

また、`value`には複数の属性を書けるので、

```CustomTextViewBindingAdapter.java
@BindingAdapter({
    "firstName",
    "lastName"
}, requireAll = false)
public static void setText(TextView textView, String firstName, String lastName) {
    String name = String.format(%s%s, 
            lastName != null ? lastName : "",
            firstName != null ? firstName : "");

    textView.setText(name);
}
```

こんな書き方もできます。
`requireAll`が`true`の場合は、指定した属性定義されている場合だけ呼ばれます（デフォルト`true`）

`android:`以外を書くか、または何も書かない場合は`app:`namespaceに割り当てられます。

デフォルトの`TextViewBindingAdapter`では、[`addTextChangedListener(TextWatcher)`の各コールバックを分割してくれている](https://android.googlesource.com/platform/frameworks/data-binding/+/android-6.0.0_r7/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java#322)ので、`android:onTextChanged`なんて属性が使えたりします。

# Tips

## イベントハンドリング

公式のサンプルだと、メソッドリファレンス使えるアピールするためか[`Hanlder`に定義する](https://developer.android.com/topic/libraries/data-binding/index.html#event_handling)みたいなめんどくさいことしてますが、普通にやるんでも大丈夫です。

```activity_main.xml
<layout>
    <data>
        <variable
            name="onTextChanged"
            type="android.databinding.adapters.TextViewBindingAdapter.OnTextChanged"/>

        <variable
            name="onOkClick"
            type="android.view.View.OnClickListener"/>
    </data>

    <LinearLayout>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onTextChanged="@{onTextChanged}"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@android:string/ok"
            android:onClick="@{onOkClick}"/>

    </LinearLayout>

</layout>
```

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        binding.setOnTextChanged((s, start, before, count) -> {
            // TextWatcher.onTextChanged()
        });

        binding.setOnOkClick((v) -> {
            // click ok button
        });
    }
}
```

`View`に`id`振って自分で`setOnClickListener()`するんでも問題ないです。

## RecyclerViewにセパレータ入れるやつ

[前に書いたやつ](http://qiita.com/ushi3_jp/items/8ccc7bc417c01754caed)

# 参考

[Data Binding Library | Android Developers](https://developer.android.com/topic/libraries/data-binding/index.html)

[bindingAdapters | Android]
(https://android.googlesource.com/platform/frameworks/data-binding/+/android-7.0.0_r29/extensions/baseAdapters/src/main/java/android/databinding/adapters)