2-wayデータバインディングが格段に実装しやすくなったAndroid data-binding 3.1.0

[データバインディングでLiveDataが使えるようになった](https://medium.com/@star_zero/databinding%E3%81%A7livedata%E3%81%8C%E4%BD%BF%E3%81%88%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F-d74b3d267ad2)みたいな話があったので、いじってみたときのメモ。

-----

![Bitmap.png](https://qiita-image-store.s3.amazonaws.com/0/74571/8f96b25d-b442-a563-1455-6ec5eb933ac1.png)

**ユーザ名とパスワード、規約にチェックしてないとsubmitボタンを押せなくする**みたいな制約条件をデータバインディングで実現するのが今回のテーマです。


## レイアウトXML　→いままでどおり

```xml:res/layout/activity_signup.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <variable
            name="viewModel"
            type="io.github.yusukeiwaki.twowaydatabindingpractice.SignupActivityiewModel"/>
    </data>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="ユーザ名"
            android:layout_margin="8dp">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:text="@={viewModel.username}"/>
        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="パスワード"
            android:layout_margin="8dp">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:text="@={viewModel.password}"/>
        </android.support.design.widget.TextInputLayout>

        <CheckBox
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:checked="@={viewModel.termOfUse}"
            android:layout_margin="8dp"
            android:text="利用規約に同意"/>

        <Button
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{viewModel.canSubmit}"
            android:layout_margin="8dp"
            android:text="Signup"/>
    </LinearLayout>
</layout>
```

## ビューモデル →もうBaseObservableとはおさらばできるぞ！

いままでのデータバインディングで2-wayなバインディングをやろうとすると、おそらくこんな感じになるはず。

```kotlin:SignupActivityiewModel
class SignupActivityiewModel : BaseObservable() {
    @Bindable
    var username: String? = null
        set(value) {
            field = value
            notifyPropertyChanged(BR.username)
            notifyPropertyChanged(BR.canSubmit)
        }

    @Bindable
    var password: String? = null
        set(value) {
            field = value
            notifyPropertyChanged(BR.password)
            notifyPropertyChanged(BR.canSubmit)
        }

    @Bindable
    var termOfUse: Boolean = false
        set(value) {
            field = value
            notifyPropertyChanged(BR.termOfUse)
            notifyPropertyChanged(BR.canSubmit)
        }

    private fun isValid() =
            !username.isNullOrBlank() && !password.isNullOrBlank() && termOfUse

    @get:Bindable
    val canSubmit: Boolean
        get() = isValid()
}
```

詳しくは以下の記事などが参考になる。

* [Androidの双方向(2-way)DataBinding](https://qiita.com/takaaki7/items/91d34e8bf9ad5d71ddd2)
* [KotlinでData Bindingをする - Observable Object編](https://qiita.com/kykomi/items/01ee72f82c6f36db0c16)

これ、実際に実装してみるとわかるのだけど、

* BaseObservable継承しないといけないので、（アーキテクチャコンポーネントの）ViewModelは継承できない！
    * 画面回転時にViewModelが再...(以下略
* `@Bindable` とか `notifyPropertyChanged(BR.**)` とか特殊な実装をいろいろやらないといけない
    * テスト書きづらい（というか書けない...）


などなど、わりと面倒なところもあって、実装コストvs効果を考えると正直あまり積極的に採用したいとは思えなかった。

完全に余談だけども、RxBindingとか使って

```kotlin:SignupActivity
override fun onCreate(...) {
  super.onCreate(...)
  binding = DataBindingUtil.setContentView(...

  // region: フォームバリデーション
  val usernameObservable = RxTextView.textChanges(binding.username)
  val passwordObservable = RxTextView.textChanges(binding.password)
  val termOfUseObservable = RxCompoundButton.checkedChanges(binding.termOfUse)

  val validationObservable = Observable.combineLatest(usernameObservable, passwordObservable, termOfUseObservable) { username, password, termOfUse ->
    !username.isNullOrBlank() && !password.isNullOfBlank() && termOfUse
  }
  validationObservable
      .compose(bindToLifecycle())
      .subscribe(RxView.enabled(binding.submitButton))
  // endregion
}

```

みたいに書いたほうがよっぽどスッキリ2-wayバインディングが書けるよね！　ってのが本音だった。

　

　

### これが、data-binding 3.1.0になると・・・

```kotlin:SignupActivityiewModel
class SignupActivityiewModel : ViewModel() {
    val username = MutableLiveData<String>()
    val password = MutableLiveData<String>()
    val termOfUse = MutableLiveData<Boolean>()

    private fun isValid() =
            !username.value.isNullOrBlank() && !password.value.isNullOrBlank() && termOfUse.value == true

    val canSubmit = MediatorLiveData<Boolean>().also { result ->
        result.addSource(username) { result.value = isValid() }
        result.addSource(password) { result.value = isValid() }
        result.addSource(termOfUse) { result.value = isValid() }
    }
}
```

```kotlin:SignupActivity
class SignupActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySignupBinding
    private lateinit var viewModel: SignupActivityiewModel

    override fun onCreate(...) {
        super.onCreate(...)
        binding = DataBindingUtil.setContentView(...

        // region: setup ViewModel
        viewModel = ViewModelProviders.of(this).get(SignupActivityiewModel::class.java)
        binding.setLifecycleOwner(this)
        binding.setViewModel(viewModel)
        // endregion
    }
}
```

LiveDataをバインディングできるようになったおかげで、データの流れがMediatorLiveDataで大変わかりやすく書けるようになった。
あと、アーキテクチャコンポーネントのViewModelベースで使用することもできるようになり、画面回転がかかわるユースケースでも割と平気になった。

![image](https://user-images.githubusercontent.com/11763113/37961268-cef8ebd4-31f2-11e8-9635-aa79514dc77b.gif)

　

## databinding v2と compiler 3.1.0

よくわかってなくて、@star_zero さんからコメントで指摘をいただきました。(ありがとうございました!

v2だからLiveData対応したというわけではなくて、v1のままでも `apply plugin: 'kotlin-kapt'` すればLiveDataは使えました。