Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

【Android】MediatorLiveData で他の LiveData を監視する

今回は、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 に表示されるようになります。

参考

MediatorLiveData