今回は、MediatorLiveData で他の LiveData を監視する方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Bumblebee | 2021.1.1 Patch 2
- JDK 11.0.11
- Android Gradle Plugin 7.1.2
- Kotlin 1.6.10
- Gradle 7.4.1
- androidx.lifecycle 2.4.1
MediatorLiveData とは?
MediatorLiveData は LiveData のサブクラスであり、他の LiveData の値を監視して自身の値を変更することができます。
以下が、監視対象を追加するメソッドです。
@MainThread public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged)
引数 source に監視対象の LiveData を設定します。そして、引数 onChanged に引数 source が変更されたときに呼び出されるコールバックを設定します。
なお、1 つの MediatorLiveData で、2 つ以上の LiveData を監視することが可能です。
// OK
mediator.apply {
addSource(source1) { mediator = it }
addSource(source2) { mediator = it }
}
しかし、MediatorLiveData は 1 つの LiveData に対して 2 つ以上のコールバックを設定することができません。
以下のように、すでに監視を開始している LiveData に対して、異なるコールバックを設定した場合、MediatorLiveData#addSource は IllegalArgumentException を投げます。
// NG mediator.apply { addSource(source1) { mediator = it } addSource(source1) { mediator = "source1: $it" } }
監視を停止したい場合には、以下の MediatorLiveData#removeSource を使います。
@MainThread public void <S> removeSource(@NonNull LiveData<S> toRemote)
MediatorLiveData を使う
実際に MediatorLiveData を試してみます。
今回は、Activity と ViewModel、そして、String 型の値を持つ LiveData を 2 つとそれらを結合する MediatorLiveData を 1 つ用意します。MediatorLiveData は、2 つの LiveData を監視し、それらが変更されるたびに 2 つの LiveData を結合して自身に格納します。
上記の 3 つの LiveData を ViewModel に保持し、Activity から結合元となる 2 つの LiveData の変更と MediatorLiveData の監視を行います。
ライブラリのインポート
MediatorLiveData を使えるようにするために、アプリの build.gradle ファイルに次の依存関係を追加します。
dependencies { implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" }
lifecycle-livedata-ktx
をインポートすることにより、MediatorLiveData が使えるようになります。また、以降のサンプルでは LiveData を ViewModel に保持するため、lifecycle-viewmodel-ktx
もインポートします。
ViewModel で値の保持と結合を行う
まず、ViewModel を以下のように実装します。
class MainViewModel : ViewModel() { // editText1 の文字列 val str1 = MutableLiveData("") // editText2 の文字列 val str2 = MutableLiveData("") // editText1 と editText2 の結合 val linkedStr: MediatorLiveData<String> = MediatorLiveData() init { linkedStr.apply { // str1 の変更を監視する。 addSource(str1) { linkedStr.value = "$it ${str2.value!!}" } // str2 の変更を監視する。 addSource(str2) { linkedStr.value = "${str1.value!!} $it" } } } }
監視の開始を init で行います。2 つの LiveData ごとに addSource を呼び出します。監視対象が変更されたら、もう一方の LiveData と結合して、自身に格納します。
Activity で LiveData の変更と MediatorLiveData の監視を行う
そして、Activity を以下のように実装します。
class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider(this)[MainViewModel::class.java] // editText1 の文字列変更を str1 に反映する。 findViewById<EditText>(R.id.editText1) .doOnTextChanged { text, _, _, _ -> viewModel.str1.value = (text ?: "").toString() } // editText2 の文字列変更を str2 に反映する。 findViewById<EditText>(R.id.editText2) .doOnTextChanged { text, _, _, _ -> viewModel.str2.value = (text ?: "").toString() } viewModel.linkedStr.observe(this) { findViewById<TextView>(R.id.text).text = it } } }
Activity のレイアウトには、2 つの LiveData ごとに EditText を用意し、それらのテキストの変更を監視し、テキストを ViewModel の LiveData に格納します。
そして、2 つの LiveData を結合した linkedStr
を監視し、その値を TextView に表示します。
こうすることにより、2 つの EditText に入力したテキストを結合したものが TextView に表示されるようになります。