LiveData를 사용할 때 변수가 아닌 변수의 Property만 변경된 경우, LiveData를 Observe 하는 Observer에게 notify 되지 않는다.

대상 타입 설정하기

이때, Bindable을 활용하면 property가 변경되었을 때, notify 되도록 구현할 수 있다.

  1. BaseObservable을 상속하도록 수정한다.
  2. 변경되었을 때, notify 되도록 할 property에 @Bindable 어노테이션을 붙인다.
  3. Property를 변경한 후, notifyPropertyChanged(BR.변수이름) 을 호출한다.
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable

open class Player(val name: String) : BaseObservable() {
    @Bindable
    private val cards = ArrayList<Card>()

    fun add(newCards: List<Card>) {
        cards.addAll(newCards)
        notifyPropertyChanged(BR.cards)
    }

    fun getCards() = cards.toList()

    fun reset() {
        cards.clear()
        notifyPropertyChanged(BR.cards)
    }
}

CustomMutableLivedata

property가 변경되면 observer에게 notify 하는 callback을 가진 MutableLiveData를 구현한다.

import androidx.databinding.Observable
import androidx.lifecycle.MutableLiveData

class CustomMutableLiveData<T: Any> : MutableLiveData<T>(){

    private val callbacks = HashMap<Observable, Observable.OnPropertyChangedCallback>()

    fun observeList(list: List<Observable>?) {
        clearCallBack()

        list?.forEach { observable ->
            val callback = object: Observable.OnPropertyChangedCallback() {
                override fun onPropertyChanged(sender: Observable, propertyId: Int) {
                    value = value
                }
            }
            observable.addOnPropertyChangedCallback(callback)
            callbacks[observable] = callback
        }
    }

    override fun onInactive() {
        super.onInactive()

        clearCallBack()
    }

    private fun clearCallBack() {
        callbacks.forEach { (observable, callback) ->
            observable.removeOnPropertyChangedCallback(callback)
        }
        callbacks.clear()
    }
}

사용하기

class GameViewModel : ViewModel() {

    private val _players: CustomMutableLiveData<List<Player>> by lazy {
        CustomMutableLiveData<List<Player>>().also { customMutableLiveData ->
            val initialPlayers = listOf<Player>()
            customMutableLiveData.value = initialPlayers // 1. 값을 설정하고
            customMutableLiveData.observeList(initialPlayers) // 2. observeList를 호출한다.
        }
    }

...
}

문제점

위와 같이 구현하면 Player 자체가 바뀌지 않았음에도, Player의 Observer에게 notify된다.

override fun onStart() {
        super.onStart()

        val playersObserver = Observer<List<Player>> { newPlayers ->
            (binding.recyclerView.adapter as PlayerAdapter).updatePlayer(newPlayers)
        }

        viewModel.players.observe(viewLifecycleOwner, playersObserver)
    }

또한 어떤 Player의 어떤 property가 바꼈는지 알 수 없기 때문에, notifyDataSetChanged() 를 호출해야 해서 비효율적이다.

class PlayerAdapter : RecyclerView.Adapter<PlayerAdapter.ViewHolder>() {

....

		fun updatePlayer(newPlayers: List<Player>) {
        if (players == newPlayers) {
            notifyDataSetChanged()
            return
        }
				players.clear()
        players.addAll(newPlayers)
        notifyDataSetChanged()
    }

PropertyChagnedCallback을 Player의 ViewHolder로 옮기기

이전에 CustomMutableLiveData이 하던 Property 감지 역할을 ViewHolder로 옮긴다. 이렇게 할 경우, 모든 Player가 아닌, property가 변경된 Player만 호출되며, 바뀌지 않은 Players 리스트에 대한 notify가 발생하지 않는다.

ViewModel에서 사용하던 CustomMutableLiveData 대신 기본 MutableLiveData로 변경한다.