RxJava の Observable と LiveData と ObservableField をいい感じで使おう

LiveData について勘違いしていたことをいくつか からの続きです。

前記事では LiveData は、

  • 購読解除を自動でやってくれるので便利
  • DataBinding(=ObservableField)としては使えない
  • 最低限の合成しかできないので物足りない

という事を書きました。

今回の記事では、上で挙げた微妙な3つの点を解消すべく、RxJava と LiveData と DataBinding をいい感じで併用してみたいと思います。今回もコードは Kotlin です。

RxJava with Android DataBinding

RxProperty を使おう!

はい終了。

RxProperty について書くの何度目なんだ、自分。
作者の @k-kagurazaka@github さんにもお世話になりっぱなしだし、サイコーです、大好きです RxProperty、もっと :star2: を!!

RxProperty<T> は基本的には Observable<T>(というか Subject<T>)なのですが、 .value プロパティで ObservableField<T> に変換できます。

// MainViewModel.kt
class MainViewModel : ViewModel() {

    // GitHub ユーザー名。EditText.text から双方向(TwoWay)バインドされる。
    val user = RxProperty<String>()

    init {
        user.set("hogehoge")
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="net.amay077.livedatasample.view.MainActivity"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="GitHub user name"
            android:text="@={viewModel.user.value}"/>  ←-- user.value とすることで ObservableField に!
    </LinearLayout>
</layout>

このように レイアウトXMLに、viewModel.user.value と記述するとデータバインディングできちゃいます。上記例ではちゃんと双方向バインディングも効きます。

RxJava with LiveData

これは 「RxJava の Observable<T> から LiveData<T> に変換する拡張メソッド」を作ってやりましょう。

// ObservableExtensions.kt

/**
 * Observable<T> を LiveData<T> に変換
 */
fun <T> Observable<T>.toLiveData() : LiveData<T> {

    return object : MutableLiveData<T>() {
        var disposable : Disposable? = null;

        // ライフサイクルがActiveになったときに購読開始
        override fun onActive() {
            super.onActive()

            // Observable -> LiveData
            disposable = this@toLiveData.subscribe({
                this.postValue(it)
            })
        }

        // ライフサイクルが非Activeになったら購読停止
        override fun onInactive() {
            disposable?.dispose();
            super.onInactive()
        }
    }
}

ライフサイクルが非アクティブ(具体的には onPause)になったときに購読停止してあげればきっと大丈夫なはず。

次のような感じで使えます。

// MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        <いろいろ省略>

        // RxProperty を LiveData に変換。
        val liveDataUser = viewModel.user.toLiveData()

        // LiveData を購読
        liveDataUser.observe(lifecycleOwner, Observer { 
            editUserName.setTextKeepState(it)
        })
    }
}

通常は RxProperty.value で、DataBinding すればよいと思うんですが、 BindingAdapter を作るのが面倒とか、View側でちょっと手の込んだことをしたい場合には .toLiveData() で LiveData に変換して、安全な購読管理を享受できます。

LiveData を直接使った方がよいケース

RxProperty -> LiveData する時の注意点として、値が変更時しか通知されない、というものがあります。
昨日書いた ように、 LiveData の特性は、同値チェックは特になく値が設定されれば onChanged を通知するのですが、 RxProperty は「変更通知プロパティ」なので(RxJava 風に言うと distinctUntilChanged なので)、同じ値を連続で設定しても最初しか通知されません。

そのため、Model->ViewModel->Viewの方向へ、値をただ垂れ流して、View側で受信して何かしたい場合は、LiveData をそのまま使うのがよいでしょう。これは EventBus(Messenger) 的な使い方です。

// MainViewModel.kt
class MainViewModel : ViewModel() {

    // View 側から購読して Toast を表示するための LiveData。
    // 変更通知が必要ない(=EventBus的に使う)なら、LiveData をそのまま使うのがいいんじゃなイカ。
    private val _toast = MutableLiveData<String>()
    val toast : LiveData<String> = _toast

    fun showToast(view:View) {
        toast.set("トーストだよ")
    }
}
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        <いろいろ省略>

        // Toast を表示するために、 toast:LiveData を購読する。
        viewModel.toast.observe(this, Observer { message ->
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        })
    }
}

上記の例は、トーストを表示するために LiveData<String> を使用しています。
他にはダイアログボックスの表示や、画面遷移の要求メッセージを View 側に通知するためには LiveData をそのまま使うのが良いと思います(というかそこを Observable にする必要を感じない)。

完成系

前回、サンプルとして GitHub のリポジトリ一覧を検索するアプリを作っていたんでしたね。

それを、

  • DataBinding
  • LiveData
  • RxJava
  • RxProperty

全部使って書いてみたコードがこちら↓です。

スクリーンショットはこんなの。

Untitled.gif

AAC 時代でも Observable centric な考えでいいんじゃないでしょうか。
図にまとめるとこんな感じです。

snap.png

RxProperty が流行らない理由が分からないのです。みなさん使ってみてくださいね!