LiveData について勘違いしていたことをいくつか からの続きです。
前記事では LiveData は、
- 購読解除を自動でやってくれるので便利
- DataBinding(=ObservableField)としては使えない
- 最低限の合成しかできないので物足りない
という事を書きました。
今回の記事では、上で挙げた微妙な3つの点を解消すべく、RxJava と LiveData と DataBinding をいい感じで併用してみたいと思います。今回もコードは Kotlin です。
RxJava with Android DataBinding
RxProperty を使おう!
はい終了。
RxProperty について書くの何度目なんだ、自分。
作者の @k-kagurazaka@github さんにもお世話になりっぱなしだし、サイコーです、大好きです RxProperty、もっと を!!
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")
}
}
<?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
全部使って書いてみたコードがこちら↓です。
スクリーンショットはこんなの。
AAC 時代でも Observable centric な考えでいいんじゃないでしょうか。
図にまとめるとこんな感じです。
RxProperty が流行らない理由が分からないのです。みなさん使ってみてくださいね!