Android-Transformations.distinctUntilChanged()소개

通常、LiveDataにデータが設定されていると、このデータを監視しているObserverに更新イベントが送出されます。以前に設定したデータと同じ値を再設定しても、Observerに毎回イベントを配信します。場合によっては、不要なタスクを何度も実行することができます。

distinctUntilChanged() は、このような部分を解決するために導入されたライブラリです。このメソッドは、重複は無視し、実際のデータに変更がある場合にのみ更新イベントを送出するLiveDataを生成します。このように生成されたLiveDataを使用すると、重複値が設定されても更新イベントが発生しないため、Observerが更新イベントを受信しなくなります。

/**
 * Creates a new {@link LiveData} object does not emit a value until the source LiveData value
 * has been changed.  The value is considered changed if {@code equals()} yields {@code false}.
 *
 * @param source the input {@link LiveData}
 * @param <X>    the generic type parameter of {@code source}
 * @return       a new {@link LiveData} of type {@code X}
 */
@MainThread
@NonNull
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source)

androidx.lifecycle:lifecycle-*:2.1.0バージョンで Transformations.distinctUntilChanged() が追加されました。

1. Example : distinctUntilChanged() で LiveData オブジェクトを作成する

distinctUntilChanged()でLiveDataを作成し、同じ値を設定したときにObserverに更新イベントが送出されないことを確認します。

サンプルアプリは、ボタンをクリックするとEditTextに入力された数値をLiveDataに設定し、Observerに更新イベントが渡されるとそのデータをTextViewに表示します。

livedata Transformations.distinctUntilChanged

次のようにサンプルアプリを実装しました。完全なコードはGitHub - LiveDataDistinctUntilChangedSampleに載せたので、詳細はこちらをご覧ください。

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val viewModel = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
        binding.lifecycleOwner = this
        binding.viewModel = viewModel

        binding.button.setOnClickListener {
            val input = binding.editText.text.toString().toInt()
            Log.d("MainActivity", "Button clicked, input value: $input")
            viewModel.setValue(input)
        }

        viewModel.value.observe(this) {
            Log.d("MainActivity", "LiveData updated: $it")
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable name="viewModel"
            type="com.example.distinctuntilchanged.MainActivityViewModel" />
    </data>

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

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.value.toString()}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/editText"
            android:layout_width="240dp"
            android:layout_height="wrap_content"
            android:inputType="number"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/textView" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CLICK"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/editText" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MainActivityViewModel.kt

class MainActivityViewModel : ViewModel() {

    private val _value: MutableLiveData<Int> = MutableLiveData()
    val value: LiveData<Int> = Transformations.distinctUntilChanged(_value)

    fun setValue(value: Int) {
        _value.postValue(value)
    }
}

動作確認

Appを実行して、次のように同じデータをLiveDataに複数回セットしました。

  1. EditTextに 123を入力してボタンを3回クリック
  2. EditTextに 1234を入力してボタンを3回クリック

結果を見ると、LiveDataに同じvalueがセットされたときに更新イベントは発生しませんでした。

01-09 10:56:38.746  6440  6440 D MainActivity: Button clicked, input value: 123
01-09 10:56:38.748  6440  6440 D MainActivity: LiveData updated: 123
01-09 10:56:41.329  6440  6440 D MainActivity: Button clicked, input value: 123
01-09 10:56:43.234  6440  6440 D MainActivity: Button clicked, input value: 123

01-09 10:56:47.009  6440  6440 D MainActivity: Button clicked, input value: 1234
01-09 10:56:47.010  6440  6440 D MainActivity: LiveData updated: 1234
01-09 10:56:48.053  6440  6440 D MainActivity: Button clicked, input value: 1234
01-09 10:56:48.745  6440  6440 D MainActivity: Button clicked, input value: 1234

2. Example : distinctUntilChanged() を使わずに、LiveData オブジェクトを生成する

次のように distinctUntilChanged() を使わずに、LiveData を生成しました。他のコードは上記と同じです。

class MainActivityViewModel : ViewModel() {

    private val _value: MutableLiveData<Int> = MutableLiveData()
    val value: LiveData<Int> = _value

    fun setValue(value: Int) {
        _value.postValue(value)
    }
}

Appを実行して、次のように同じデータをLiveDataに複数回セットしました。

  1. EditTextに 123を入力してボタンを3回クリック
  2. EditTextに 1234を入力してボタンを3回クリック

結果を見ると、同じデータがLiveDataに設定されたときに更新イベントが発生し、Observerに渡されました。

01-09 11:09:26.190  6906  6906 D MainActivity: Button clicked, input value: 123
01-09 11:09:26.191  6906  6906 D MainActivity: LiveData updated: 123
01-09 11:09:26.778  6906  6906 D MainActivity: Button clicked, input value: 123
01-09 11:09:26.779  6906  6906 D MainActivity: LiveData updated: 123
01-09 11:09:27.378  6906  6906 D MainActivity: Button clicked, input value: 123
01-09 11:09:27.380  6906  6906 D MainActivity: LiveData updated: 123

01-09 11:09:28.930  6906  6906 D MainActivity: Button clicked, input value: 1234
01-09 11:09:28.932  6906  6906 D MainActivity: LiveData updated: 1234
01-09 11:09:29.474  6906  6906 D MainActivity: Button clicked, input value: 1234
01-09 11:09:29.476  6906  6906 D MainActivity: LiveData updated: 1234
01-09 11:09:30.002  6906  6906 D MainActivity: Button clicked, input value: 1234
01-09 11:09:30.003  6906  6906 D MainActivity: LiveData updated: 1234

3. Transformations.distinctUntilChanged() のライブラリ実装内容

AOSP - Introduce Transformations.distinctUntilChanged でこのメソッドの実装内容を確認できます。 コードを見ると、 onChangedが発生したとき、以前のデータと異なるときだけ setValue()をして更新をするように実装されています。

References

codechachaCopyright ©2019 codechacha