60
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-27

データバインディングで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は使えました。

60
38
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?