他のLiveDataをObserveできるMediatorLiveData

プロジェクト内でちょくちょくMediatorLiveDataを使っている場所があり、調べてみたのですが今一つ腑に落ちなかったので自分で触ってみました🧑🏻‍💻

今回書いたコードは[Github](https://github.com/yasukotelin/MediatorLiveDataSample)に挙げてるのでこちらも参照してみてください🧜🏻‍♂️

[公式のMediatorLiveDataのページ](https://developer.android.com/reference/androidx/lifecycle/MediatorLiveData)が完結でわかりやすかったのですが、MediatorLiveDataは **他のLiveDataをObserveできるLiveData** です。

例えば、EditTextに入力された文字を表示しつつ、文字数も表示する、といったケースで考えてみます。

```kotlin:MainViewModel.kt
val message: MutableLiveData<String> = MutableLiveData()
val count: MediatorLiveData<Int> = MediatorLiveData()
```

画面に表示するmessageは通常のMutableLiveDataで宣言しています。
このmessageを監視して、文字数をカウントするLiveDataを `MediatorLiveData` として宣言してみました。

countがmessageを監視する、と言うのは `addSource` を使ってこう書きます。

```kotlin:MainViewModel.kt
count.addSource(message) {
    val cnt = message.value?.length ?: 0
    count.postValue(cnt)
}
```

```
count.addSource(監視したい対象のLiveData) {
  // 監視したい対象のLiveDataが変更された時の処理
}
```

これで、messageが変更されたときに自動で文字数がカウントされて、countにpostValueされるようになります。messageの変更は通常のLiveDataを同じように、更新用の関数を用意してあげて、View側から呼んであげる感じです。


```kotlin:MainViewModel.kt
fun postMessage(message: String) {
    this.message.postValue(message)
}
```

MainViewModelの全体はこんな感じ。

```kotlin:MainViewModel.kt
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が変更されます！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166629/8403bc6e-1306-e599-0580-7db3bf4bd524.gif" width="300px" />


ちなみに、MediatorLiveDataを使わないで、messageを更新してから自分でcountも更新すれば同じじゃないの？？と思うかもしれませんが、それだとうまく行きません。

```kotlin:MainViewModel.kt
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)
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166629/03f54c4c-c690-1d9c-c371-632b0bd26223.gif" width="300px" />

入力された文字数とカウントが合ってませんね。LiveDataの `postValue` はUIスレッドとは別スレッドで更新するメソッドです。そのため非同期で処理がされるため、countをmessageの値を使って更新しようと思うと同期が取れてなくて期待した動きにならないといったわけです。

MediatorLiveDataを使えば、postValueで値が変更された**後**に発火するイベントを定義できるということがわかりました🤷🏻‍♂️

さらにもうちょっと遊んでみます。

入力されたmessageが6文字以上だったらOKで、それ以外はNGにするといったよくあるやつをMediatorLiveDataを使って書いてみます。

プロパティは新しくcompleteMessageをMediatorLiveDataで追加しただけです。

```kotlin:MainViewModel.kt
val message: MutableLiveData<String> = MutableLiveData()
val count: MediatorLiveData<Int> = MediatorLiveData()
val completeMessage: MediatorLiveData<String> = MediatorLiveData()
```

監視の仕方はこんな感じにしてみます。
View -> messageを更新！
count -> messageを監視。自動で文字数を更新
completeMessage -> countを監視。6文字以上ならOKのメッセージ。

```kotlin:MainViewModel.kt
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
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166629/747227fa-5f11-cde0-af09-0d498b96209d.gif" width="300px" />

MediatorLiveDataを使うことでなかなかにシンプルに書くことができました！