KotlinでAndroid Data Bindingを使ってみた

Kotlinで書いたアプリに[Data Binding](http://developer.android.com/intl/ja/tools/data-binding/guide.html)を導入してみたので、そのメモとなります。ButterKnifeの代わりに使ったり、XML側に動的な値を設定できる便利なやつですね。

## 準備
- build.gradleでdataBindingを有効にします。

```
android {
    ・・・
    dataBinding {
        enabled = true
    }
}
```

- こちらの記事を参考にkaptでcompilerを追加します。これがないとDataBindingで生成された中間クラスがKotlinから見えません。
    - [KotlinでData Bindingを使う時の設定](http://qiita.com/umetsu/items/487d38be86c31ff59075)

## 使い方
- bindingさせたいViewのレイアウトファイルを開いて以下のようにlayoutタグで全体を囲みます。この状態でビルドするとBinding用のファイルが作成されます。`activity_main.xml`なら`ActivityMainBinding`クラスが生成されます。

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

- Kotlin側ではJavaで書く時と同じように使えます。

### Activityで使う場合

```kotlin:Activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    // binding経由でViewを参照できる
    binding.hogeButton.setOnClickListener { v ->
        // ボタンを押した時の処理
    }
}
```

### Fragmentで使う場合

```kotlin:Fragment.kt
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    // ジェネレートされたBindingクラスからViewをinflateできる
    val binding = FragmentListBinding.inflate(inflater, container, false)

    // このようにも書ける
    // val binding = DataBindingUtil.inflate<FragmentListBinding>(inflater, R.layout.fragment_list, container, false)

    ...

    // 返すのはbindingではなくview
    return binding.root
}
```

### ListのAdapter内で使う場合

```kotlin:Adapter.kt
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {

    return convertView?.apply {
        // Viewが生成済なら使い回す
        val binding = DataBindingUtil.getBinding<ListItemBinding>(this)
        bindEntity(binding, getItem(position))
    } ?: let {
        // Viewが未生成なら新しくbindingを作成してからViewを返す
        val binding = ListItemBinding.inflate(mLayoutInflater, parent, false)
        bindEntity(binding, getItem(position))
        return binding.root
    }
}

private fun bindEntity(binding: ListItemBinding , entity: Entity) {
    // 画面に表示する
}
```

### Kotlin側から値をbindする

- このユーザー情報の名前を表示するという体

```kotlin:UserEntity
data class UserEntity(val name: String) 
```

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

    <!-- bindさせたいクラスを指定する -->
    <data>
        <variable
            name="userEntity"
            type="me.rei_m.sample.entities.UserEntity" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

            <!-- dataで定義したインスタンスを参照できる。いまのところIDE上ではサジェストされないので `userEntity.name` を手打ちする必要はある -->
            <android.support.v7.widget.AppCompatTextView
                android:id="@+id/fragment_text_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{userEntity.name + &quot; さん&quot;}" />
    </LinearLayout>
</layout>
```

```kotlin:Fragment.kt
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    val binding = FragmentBinding.inflate(inflater, container, false)

    // xmlのdataタグに定義したプロパティが生えているので値をセットする
    binding.userEntity = UserEntity(name = "hogehoge")

    ...

    return binding.root
}
```

- これでFragment側のTextViewには `hogehoge さん` が表示されるようになります。今までのようにクラス側で表示したいViewを探してtext属性にセットみたいなことはやらなくてよくなります。

### xml内で別のxmlをincludeしている場合
- includeする側でidをふればBindingから参照できるようになります。また、includeされる側もlayoutタグで囲んでおけば同様にBindingクラスがジェネレートされます。下の例だと `binding.layoutSubView`で参照できるようになってlayoutSubViewの型は`LayoutSubViewBinding`になります
- includeにbind属性をつけると読み込まれる側にbindできるのでKotlin側でDataをセットする手間が省けます。includeする側とされる側で同じデータを扱う場合はこのように書いたほうがいいでしょう。

```xml:fragment.xml
・・・

<include
    android:id="@+id/layout_sub_view"
    layout="@layout/layout_sub_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    bind:userEntity="@{userEntity}" />

・・・
```

## 書いていて困ったこと

~~### xml側でstaticメソッドが使えなかった~~
- これ出来たので修正しました。

- campanion objectに生やす形だとJavaからは○○クラスのCampanionに生えている形になるので見えません。

```kotlin
class HogeUtil private constructor() {
    // これはxmlから見えない
    companion object {
        fun fuga() {
            ・・・
```

- object構文にJvmStaticアノテーションをつけた関数を生やしてxmlでimportすると見えるようにります。BindingAdapterも同様の形式でいけました。

```kotlin
object HogeUtil {
    // これはxmlから見える
    @JvmStatic
    fun fuga() {
        ・・・
```


~~### xml側で条件式が使えなかった~~
~~- 同じく公式のコードから引用してますが、bindしたDataのプロパティに応じて表示、非表示を切り替えるみたいなコードを書いたのですが、ビルドは通りませんでした。~~

- こちらで解消するみたいです。
    - http://qiita.com/MeilCli/items/b2cf624d21a8139a1a99

### ビルドが不安定？
- これが一番不思議（自分の理解不足なだけかも。。。）なのですが、ビルドが安定しませんでした。具体的に起きた事象としては生成されたはずのBindingクラスがビルド後にAndroid Studio上で見えたり見えなくなったりしました。具体的には以下のパターン。
    - 生成されたBindingクラスのinflateを使おうとするとinflateメソッド自体が見えなくなっていて、参照できるのは親クラスのViewDataBindingのメソッド・プロパティだけになる。DataBindingUtilのinflateは怒られない。
    - DataBindingUtilのinflateすらできなくなる。生成されたクラスそのものが見えない。
- 上記の現象は何回かビルドを繰り返したり、buildディレクトリをまるごと削除してAndroidStudioを再起動してビルドし直したりすると直りました。この辺の問題が自分だけの問題かがわからないのですが知見を持っている方がいればコメントいただけると幸いです。