Android LiveDataの2-way バインディングが動かなくて2日潰した

Android LiveDataの2-way バインディングが動かなくて2日潰した

久しぶりにAndroidの最新技術をキャッチアップ中です。

RxJavaやKotlin、Architecture components、AndroidXなどなど。まだまだ新しい物が増えているようです。。

まず、自分のアプリにRxJava(RxKotlin)を導入しています。また、なんちゃってMVVMだったのですが、最近はAndroidがViewModelを採用しているようなので、最新のデファクタに寄せてみようと思ってました。

早速詰まったのが、LiveDataの2wayバインディング。いままではDataBindingを利用していたのですが、ViewModelとは、LiveDataが相性がいい模様。さらにAndroid Studio 3.1(?)から、DataBindingとLiveDataが連携できるようになったとのこと。

早速、バインディングしてみたのですが1wayはうまくいくのですが、2wayが動かないです。どのようなUIかというと、単純なEditTextとButtonがあって、EditTextが空でなければ、Buttonがenabled=true。空の場合、enabled=falseとしたいのです。

RxJavaの不慣れとあわせて、ここで2日も潰すはめに。。。

レイアウトイメージは、シンプルにこんな感じ

<layout>
<!-- 中略 -->

<EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/edit"
    android:text="@{viewModel.text}"
    android:lines="1"
    android:hint="テキストを入力してください"
    android:inputType="text"/>

<Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="更新"
    android:id="@+id/button"
    android:onClick="@{viewModel::onClick}"
    android:enabled="@{safeUnbox(viewModel.canUpdate)}"/>
<!-- 以下略 -->

ViewModelはこんな感じです。書き換えているので、コンパイルなどは気にしてません。

class MyViewModel: ViewModel() {
    var text: MediatorLiveData<String> = MediatorLiveData()
    val canUpdate = MediatorLiveData<Boolean>()

    private fun isCanUpdate(): Boolean {
        val t = text.value
        return t != null && t.isNotEmpty()
    }

    init {
        // textと連動するように変更
        canUpdate.addSource(text) { canUpdate.value = isCanUpdate() }
    }

さて、なぜ動かないのか。。。

結論は公式ドキュメントをちゃんと読みなさいということです。

The @={} notation, which importantly includes the “=” sign, receives data changes to the property and listen to user updates at the same time.

developer.android.com

この辺の新しい技術は公式ドキュメントのバージョン管理もわけわからないことになっているのですが、ちゃんと読みましょう。

普通のバインディングは@{xxx}としますが、2wayにしたい場合は@={xxx}と=(イコール)が必要です。なんかもっとわかりやすい表記にしてくれたらいいのに、、、。