Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

データバインディングでLiveDataが使えるようになったみたいな話があったので、いじってみたときのメモ。


Bitmap.png

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

レイアウト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なバインディングをやろうとすると、おそらくこんな感じになるはず。

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()
}

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

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

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

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

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

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になると・・・

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() }
    }
}
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

 

databinding v2と compiler 3.1.0

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした