Posted at
RxJavaDay 5

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

More than 1 year has passed since last update.

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 が流行らない理由が分からないのです。みなさん使ってみてくださいね!