Search code examples
kotlinandroid-livedatamutablelivedata

LiveData of a list notifies observers without calling setValue or postValue


I have a MutableLiveData like this in my view model:

val liveData = MutableLiveData<ArrayList<*>>()‍‍‍‍‍

I add the results from an endpoint call to this LiveData like this:

liveData.value?.addAll(myList)

as far as I know, MutableLiveData shouldn't notify it's Observers unless you do a setValue or postValue on it but at this point when my code is run, Observers are notified of the change.

How is this possible?


Update:

I came across an even more stranger behavior, this test passes but the list gets printed one time: []

    @Test
    fun `strange live data behavior`() {
        val myLiveData = MutableLiveData<ArrayList<Int>>()
        val observer = mock() as Observer<ArrayList<Int>>
        myLiveData.observeForever(observer)
        myLiveData.observeForever { println(it) }
        myLiveData.value = ArrayList()
        verify(observer).onChanged(ArrayList())
        myLiveData.value?.addAll(listOf(1, 2, 3, 4))
        val result = ArrayList<Int>()
        result.add(1)
        result.add(2)
        result.add(3)
        result.add(4)
        verify(observer).onChanged(result)
    }


Solution

  • When a LiveData sends a notification, LiveData does not send a copy of the item. Instead, it simply pass a reference to the same instance that is holding.

    This means that if you modified the data inside the LiveData like this:

    myLiveData.value?.addAll(listOf(1, 2, 3, 4))

    The ArrayList object that the observer previously received will also be modified without Observer.onChanged() getting called, simply because they are the same object. This is why using a mutable object in LiveData or any Observer/reactive Pattern is generally not a good idea.

    To verify that onChanged() is actually called only once, add this line at the end of the test:

        @Test
        fun `strange live data behavior`() {
            val myLiveData = MutableLiveData<ArrayList<Int>>()
            val observer = mock() as Observer<ArrayList<Int>>
            myLiveData.observeForever(observer)
            myLiveData.observeForever { println(it) }
            myLiveData.value = ArrayList()
            verify(observer).onChanged(ArrayList())
            myLiveData.value?.addAll(listOf(1, 2, 3, 4))
            val result = ArrayList<Int>()
            result.add(1)
            result.add(2)
            result.add(3)
            result.add(4)
            verify(observer).onChanged(result)
    
            // Below should pass because onChanged is only called once.
            verify(observer, times(1)).onChanged(any()) 
        }