MediatorLiveData で DataBinding を少し楽にする

LiveDataを～Bindingに直接Bindできるようになり、色々と開発が捗るようになりました。
しかし、素直にLiveDataを利用するだけだと
「あれ？これもうちょっときれいにかけそうな気がするんだけど、どう書けばいいんだろう・・・」
みたいな問題にぶつかることがあります。

この記事ではそんな時に思い出してほしいMediatorLiveDataについて簡単に説明します。

## たまにぶつかる問題点
+ LiveData A と LiveData B の値から、LiveData C の値を作りたい
+ AかBのどちらかが変更されたらCもそのタイミングで変更したい

## 具体的な例
+ EditText A(prefixText) と EditText B (mainText) を準備
+ TextView C(combinedText) は、"prefixText" + "mainText" の文字列を表示する
+ prefixText または mainText の文字列が変更されたら combinedText に表示する文字列も変更する 

## layout
```xml
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity"
            >

        <EditText
                android:id="@+id/prefixText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={viewModel.prefixText}"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="prefix text"
                />

        <EditText
                android:id="@+id/mainText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={viewModel.mainText}"
                app:layout_constraintTop_toBottomOf="@+id/prefixText"
                tools:text="main text"
                />

        <TextView
                android:id="@+id/combinedText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{viewModel.combinedText}"
                android:layout_marginTop="50dp"
                android:textSize="30sp"
                app:layout_constraintTop_toBottomOf="@+id/mainText"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                tools:text="combined text"
                />
    </androidx.constraintlayout.widget.ConstraintLayout>
```

```kotlin
class MainActivityViewModel {
    val prefixText = MutableLiveData<String>().apply { value = "" }
    val mainText = MutableLiveData<String>().apply { value = "" }
    val combinedText = MutableLiveData<String>().apply { value = "" }
}
```

## 素直に書いた例
ViewModelを設定しているActivityのonCreateなどで下記のコードを書く

```kotlin
val textObserver = Observer<String> {
    val prefix = viewModel.prefixText.value ?: ""
    val main = viewModel.mainText.value ?: ""
    viewModel.combinedText.value = "$prefix-$main"
}
viewModel.prefixText.observe(this, textObserver)
viewModel.mainText.observe(this, textObserver)
```

prefixText, mainText のどちらを変更しても combinedText が変更されて、問題なく動作することが確認できます。
しかし、Activity側でLiveDataをobserveして別のLiveDataに値をセットする、というのはなんとなく気になるところです。

combinedText 自体に、prefixText, mainText を監視して自分自身の値を変更する機能があればいいのに・・・

ご安心ください。
そんな機能が用意されています。
それが今回ご紹介する`MediatorLiveData`です。

## MediatorLiveDataを利用した例
ActivityのonCreateなどに書いていたコードを削除する
ViewModelを次のように書き換える

```kotlin
class MainActivityViewModel {
    val prefixText = MutableLiveData<String>().apply { value = "" }
    val mainText = MutableLiveData<String>().apply { value = "" }
    val combinedText = MediatorLiveData<String>()

    init {
        val textObserver = Observer<String> {
            val prefix = prefixText.value ?: ""
            val main = mainText.value ?: ""
            combinedText.value = "$prefix-$main"
        }
        // prefixText が変更されたら、textObserver の処理が実行されるように設定する
        combinedText.addSource(prefixText, textObserver)
        // mainText が変更されたら、textObserver の処理が実行されるように設定する
        combinedText.addSource(mainText, textObserver)
    }
}
```

Activityに書いてあった処理がなくなり、ViewModel内でやりたいことが完結するようになりました。
Bindのためのコードも、自動生成されるActivityMainBindingからだけになり、バグも減らせそうでいい感じです。

## まとめ
+ MediatorLiveDataを利用すると、他のLiveDataの変更のタイミングで自分自身の値の変更を行うことができる
+ DataBindingと合わせて利用した場合、Bindのためのコードが自動生成されるクラスからのみになるため見通しがよくなる
+ これ、RxでいうcombineLatestでは・・・？

## おまけ(確認に使ったサンプルアプリの動き)
![MediatorLiveDataSample.gif](https://qiita-image-store.s3.amazonaws.com/0/18789/9128919e-6a06-786a-14e3-5c37f4cd420f.gif)