プロジェクト内でちょくちょくMediatorLiveDataを使っている場所があり、調べてみたのですが今一つ腑に落ちなかったので自分で触ってみました🧑🏻💻
今回書いたコードはGithubに挙げてるのでこちらも参照してみてください🧜🏻♂️
公式のMediatorLiveDataのページが完結でわかりやすかったのですが、MediatorLiveDataは 他のLiveDataをObserveできるLiveData です。
例えば、EditTextに入力された文字を表示しつつ、文字数も表示する、といったケースで考えてみます。
val message: MutableLiveData<String> = MutableLiveData()
val count: MediatorLiveData<Int> = MediatorLiveData()
画面に表示するmessageは通常のMutableLiveDataで宣言しています。
このmessageを監視して、文字数をカウントするLiveDataを MediatorLiveData
として宣言してみました。
countがmessageを監視する、と言うのは addSource
を使ってこう書きます。
count.addSource(message) {
val cnt = message.value?.length ?: 0
count.postValue(cnt)
}
count.addSource(監視したい対象のLiveData) {
// 監視したい対象のLiveDataが変更された時の処理
}
これで、messageが変更されたときに自動で文字数がカウントされて、countにpostValueされるようになります。messageの変更は通常のLiveDataを同じように、更新用の関数を用意してあげて、View側から呼んであげる感じです。
fun postMessage(message: String) {
this.message.postValue(message)
}
MainViewModelの全体はこんな感じ。
package com.github.yasukotelin.mediatorlivedatasample.ui.main
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
val message: MutableLiveData<String> = MutableLiveData()
val count: MediatorLiveData<Int> = MediatorLiveData()
init {
count.value = 0
count.addSource(message) {
val cnt = message.value?.length ?: 0
count.postValue(cnt)
}
}
fun postMessage(message: String) {
this.message.postValue(message)
}
}
実際に動かすとこんな感じで、入力された文字がmessageに流れると自動でcountが変更されます!
ちなみに、MediatorLiveDataを使わないで、messageを更新してから自分でcountも更新すれば同じじゃないの??と思うかもしれませんが、それだとうまく行きません。
package com.github.yasukotelin.mediatorlivedatasample.ui.main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
val message: MutableLiveData<String> = MutableLiveData()
val count: MutableLiveData<Int> = MutableLiveData()
fun postMessage(message: String) {
this.message.postValue(message)
updateCount()
}
private fun updateCount() {
val cnt = message.value?.length ?: 0
count.postValue(cnt)
}
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F166629%2F03f54c4c-c690-1d9c-c371-632b0bd26223.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=886f46325fbcaa893f2cc7e4a5773bc9)
入力された文字数とカウントが合ってませんね。LiveDataの postValue
はUIスレッドとは別スレッドで更新するメソッドです。そのため非同期で処理がされるため、countをmessageの値を使って更新しようと思うと同期が取れてなくて期待した動きにならないといったわけです。
MediatorLiveDataを使えば、postValueで値が変更された後に発火するイベントを定義できるということがわかりました🤷🏻♂️
さらにもうちょっと遊んでみます。
入力されたmessageが6文字以上だったらOKで、それ以外はNGにするといったよくあるやつをMediatorLiveDataを使って書いてみます。
プロパティは新しくcompleteMessageをMediatorLiveDataで追加しただけです。
val message: MutableLiveData<String> = MutableLiveData()
val count: MediatorLiveData<Int> = MediatorLiveData()
val completeMessage: MediatorLiveData<String> = MediatorLiveData()
監視の仕方はこんな感じにしてみます。
View -> messageを更新!
count -> messageを監視。自動で文字数を更新
completeMessage -> countを監視。6文字以上ならOKのメッセージ。
private val isCompleted: Boolean
get() = count.value ?: 0 >= REQUIREMENT_WORDS
init {
count.value = 0
count.addSource(message) {
val cnt = message.value?.length ?: 0
count.postValue(cnt)
}
completeMessage.addSource(count) {
val msg = if (isCompleted) "Completed!" else "$REQUIREMENT_WORDS words required"
completeMessage.postValue(msg)
}
}
fun postMessage(message: String) {
this.message.postValue(message)
}
companion object {
private val REQUIREMENT_WORDS = 6
}
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F166629%2F747227fa-5f11-cde0-af09-0d498b96209d.gif?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=92425276312919031d39aa404428163a)
MediatorLiveDataを使うことでなかなかにシンプルに書くことができました!