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 Observer
s unless you do a setValue
or postValue
on it but at this point when my code is run, Observer
s are notified of the change.
How is this possible?
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)
}
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())
}