Android - Transformations.distinctUntilChanged() 소개

일반적으로 LiveData에 어떤 데이터가 설정되면 이 데이터를 관찰하고 있는 Observer들에게 업데이트 이벤트를 전달합니다. 이전에 설정된 데이터와 동일한 값을 다시 set해도 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에 set하고, 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에 set하였습니다.

  1. EditText에 123 입력 후 버튼 3회 클릭
  2. EditText에 1234 입력 후 버튼 3회 클릭

결과를 보면, LiveData에 동일한 value가 set될 때는 업데이트 이벤트가 발생하지 않았습니다.

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에 set하였습니다.

  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

Loading script...
codechachaCopyright ©2019 codechacha