Android の Data Binding (Beta Release) を使う

**注意** 本稿は、2015-06-23 現在の [Data Binding のドキュメント](https://developer.android.com/tools/data-binding/guide.html)をもとに書いてあります。仕様や挙動は将来変更される可能性があるので気をつけてください。

# Data Binding
Google I/O 2015 で M Preview が発表され、他にも Android の新機能が発表されました。そのうちの1つが Data Binding です。

この Data Binding は、名前の通り、メモリ上のデータを XML で表されている View に結び付けるための仕組みです。

これまで、たとえばリストを表示するには、プログラム上で各 View とデータを結び付けるという作業が必要でした。今回の機能を利用することで、そのような処理を XML 内で宣言的に書けるようになります。

# 参考文献
- [Data Binding Guide | Android Developers](https://developer.android.com/tools/data-binding/guide.html)
- [Data BindingとMultidexの兼ね合いの問題を大体倒したので実用段階待ったなし - visible true](http://sys1yagi.hatenablog.com/entry/2015/06/17/190547)
- [Android DataBinding導入の第一歩: ButterKnife+ViewHolderパターンを置き換える - Islands in the byte stream](http://gfx.hatenablog.com/entry/2015/06/21/124133)

# インストール
Data Binding を利用するためには `build.gradle` に下記を記述します。

```gradle:build.gradle
dependencies {
   classpath "com.android.tools.build:gradle:1.3.0-beta1"
   classpath "com.android.databinding:dataBinder:1.0-rc0"
}
```

また jcenter がレポジトリに追加されていることも確認しておきましょう。

```gradle:build.gradle
allprojects {
   repositories {
       jcenter()
   }
}
```

各モジュールの `build.gradle` では以下の記述が必要です。

```gradle:app/build.gradle
apply plugin: ‘com.android.application'
apply plugin: 'com.android.databinding'
```

# 前提
gradle プラグインのバージョンは 1.3.0-beta1 以上である必要があります。また Android Studio 1.3 以上でないとコード補完やレイアウト表示がサポートされていません。

上記のサポートライブラリがコンパイル時に入っていれば、ランタイム時の Android のバージョンに制限はありません。

# 基本的な使い方
ここでは、POJO の `User` 型オブジェクトを表示する例を考えます。まず、レイアウトファイルを記述します。

## レイアウトファイル
`layout` 要素をルートにして、直下に `data` 要素と通常のビューのルート要素を記述します。`data` 要素には使用するデータオブジェクトとその型を記述します。

```xml:src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <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}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>
```

通常ならリソース ID やテキストを記述する箇所で `@{user.firstName}` のように `User` 型オブジェクトのフィールドを指定していることに注意してください。これによって、このビューにバインドした `User` オブジェクトの `firstName` フィールドの値が TextView に表示されます。

# バインド
さて、次にデータオブジェクトを View にバインドします。バインドするには以下のコードを実行します。

```java:src/main/java/com/example/MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}
```

以上で終わりです。MainActivity を起動してみれば、表示を確認できるでしょう。

# レイアウトファイル内の式
レイアウトファイル内では、`@{...}` という形式でバインドされるデータの値を記述しますが、ここには式を記述することが可能です。

## 使用可能な演算子
- 数値演算子 `+ - / * %`
- 文字列連結 `+`
- 論理演算子 `&& ||`
- ビット演算子 `& | ^`
- 単項演算子 `+ - ! ~`
- シフト演算子 `>> >>> <<`
- 比較演算子 `== > < >= <=`
- `instanceof`
- グルーピング `()`
- リテラル: character, String, numeric, null
- キャスト
- メソッド呼び出し
- フィールドアクセス
- 配列アクセス `[]`
- 三項演算子 `?:`

## 例
いくつか例を示します。式内でダブルクォート `"` を使うときは属性値をシングルクォート `'` で囲むか、ダブルクォートを `&quot;` でエスケープするか、ダブルクォートの代わりにバッククォート `` ` `` を使うかします。

```xml
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age &lt; 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
android:text="@{&quot;First: &quot; + user.firstName + `, Last: ` + user.lastName}"
```

## Null 合体演算子 (Null Coalescing Operator)
Java では使えない Null 合体演算子 (Null Coalescing Operator)も利用可能です。

```xml
android:text="@{user.displayName ?? user.lastName}"
```

上記の例では、左の `user.displayName` が `null` なら、右の `user.lastName` が使われます。

## NullPointerException の回避
式内では `NullPointerException` が起きないようになっています。たとえば、下記の例で `user` が `null` の場合は、式全体の `user.name` も `null` になります。また、`int` 型の `user.age` はデフォルト値、すなわち `0` になります。

```xml
android:text="@{user.name}"
android:text="@{user.age}"
```

## リスト、配列およびマップのアクセス
リスト、配列およびマップにアクセスするには `[ ]` 演算子を用います。

```xml
<data>
    <variable name="list" type="java.util.List&lt;String>"/>
    <variable name="sparse" type="android.util.SparseArray&lt;String>"/>
    <variable name="map" type="java.util.Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"
```

## リソースの使用
`@{...}` 式内では、リソース ID を使用して、リソースにアクセスすることも可能です。また String フォーマットや [Plurals リソース](http://developer.android.com/guide/topics/resources/string-resource.html#Plurals)を使うこともできます。

```xml
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
```

なお、通常の Plurals リソースを使う場合と同様に、Plurals リソース内に String フォーマットがある場合は、引数を二つ渡さなければなりません。

```xml
<plurals name="orange">
    <item quantity="one">Have an orange.</item>
    <item quantity="other">Have %d oranges.</item>
</plurals>
```

```xml
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
```

# Observable オブジェクト
実はデータバインディングでは、データオブジェクトを Observable にすることで、動的に View の更新をすることができます(ここで言う Observable は Rx の Observable とはまったくの無関係です)。

上記に示した手順では、たとえば、`User` オブジェクトの `setFirstName(String)` メソッドを呼んだときにバインドされている TextView 側に変更は通知されません。`User` クラスを Observable にすることで、その通知が可能になります。

任意のデータクラスを Observable にするには3つの方法があります。いずれかの方法を使うことで、データオブジェクトを更新したときに動的に View も更新されるようになります。

## 1. `BaseObservable` を継承させる
データクラスが `BaseObservable` を継承し、Setter に `@Bindable` アノテーションを、Getter 内で `notifyPropertyChanged(View)` を呼ぶことで、そのデータオブジェクトの変更が View に通知されるようになります。

```java:User.java
private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getFirstName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}
```

## 2. `ObservableField` を利用する
上記の方法はちょっと手間です。もっと簡単に行いたい場合は、`ObservableField` を使う方法もあります。ただし、この場合は変更を監視したいフィールドのクラスがすべて `ObservableField` になってしまいます。

```java:User.java
private static class User extends BaseObservable {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}
```

## 3. `ObservableMap` を利用する
もっと簡単に行いたい、マップで十分、という場合は `ObservableMap` を使うこともできます。

```java:MainActivity.java
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
```

# View へのアクセス
Data Binding を使ってうれしいもう1つのところは、コード内での View へのアクセスが容易になるところです。以下のように、いままで `findViewById(int)` で View へのアクセスを得ていたところを、以下のようにできます。

```java
   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   binding.firstName.setText(... // <- TextView
```

これを使うと、カスタム Adapter 内で ViewHolder の代わりに使うことができます。

```java:app/src/main/java/com/example/UserAdapter.java
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            final ItemArticleBinding binding = ItemArticleBinding.inflate(LayoutInflater.from(this.getContext()));
            convertView = binding.getRoot();
            convertView.setTag(binding);
        }

        final ItemArticleBinding binding = (ItemArticleBinding) convertView.getTag();
        Article article = this.getItem(position);
        binding.setArticle(article);
        Picasso.with(this.getContext()).load(article.getUser().getProfileImageUrl()).into(binding.userIcon);
        return convertView;
    }
```

# さらに深く利用するために
Data Binding では、さらに様々な機能が用意されています。たとえば、生成されるクラスやメソッド名を変更したり、カスタムバインディングを定義することで、データオブジェクトの画像 URL を XML 内で指定したら自動的に画像をロードするといったこともできます。

また、実際の利用については、すでにいくつか罠があることが知られています。それらについては、sys1yagi さんのブログに詳しくまとめられています。

参考: [Data Binding カテゴリーの記事一覧 - visible true](http://sys1yagi.hatenablog.com/archive/category/Data%20Binding)

# Tips
- Data Binding のエラーは分かりにくいので、もしエラーを見ても分からない場合は `--debug` オプションを付けましょう。

# まとめ
- コードを生成するだけなので、ランタイム時に Android のバージョンに依存しないのは素晴らしい
- とりあえず、ButterKnife の代わりにはなりそう