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
<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>
class MainActivityViewModel {
val prefixText = MutableLiveData<String>().apply { value = "" }
val mainText = MutableLiveData<String>().apply { value = "" }
val combinedText = MutableLiveData<String>().apply { value = "" }
}
素直に書いた例
ViewModelを設定しているActivityのonCreateなどで下記のコードを書く
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を次のように書き換える
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では・・・?