Search code examples
androidlistkotlinandroid-livedatamutablelist

How to add a new item to a MutableLiveData MutableList? (Android - Kotlin)


I have a variable:

private var a = MutableLiveData<MutableList<Int>>()

Variable that I want to assign a value to when the button is clicked.

For example for every click, add a new random int to the list, like: [] -> [5] -> [5,7] -> [5,7,3] -> // etc

How can I add an item to a MutableList of Int that is wrapped in LiveData and empty as default?

I know that in order to add a value in MutableList i should use a.value.add() but this case i have to first use a.setValue() cause variabe a is MutableLiveData. I can't get it, how i should combine this methods. Or is there another better solution?

I searched a lot, but not found the answer.


Solution

  • A LiveData stores a single value, and pushes updates to observers. When something observes it, that Observer will be called if the LiveData currently has a value. Then, whenever that value changes, every registered observer will be called again with the new value.

    To actually change that stored value, you need a MutableLiveData - this has the setValue (and similar) functions that allow you to update it. A plain LiveData (which MutableLiveData is a subclass of) doesn't allow you to change the value.

    Generally you're recommended to do this kind of thing, e.g. in your ViewModel:

    private val _myLiveData = MutableLiveData<String>("hi")
    val myLiveData: LiveData = _myLiveData
    // or 'val myLiveData: LiveData get() = _myLiveData' which doesn't create a new backing field
    

    So internally you hold this _myLiveData which is mutable, and private to the ViewModel. That allows you to call setValue on it, etc. But you also expose it publicly as myLiveData which is a non-mutable LiveData type. This means that any external consumers can't call setValue on it, because a LiveData doesn't have those.

    You're basically not telling consumers that it's actually a MutableLiveData, just that it's a LiveData in general. That keeps things cleaner, because they can't mess with it without making an effort to do so (e.g. casting it to MutableLiveData). This way, only the owner of the data can set the value on it - everything else only reacts to changes. Anything that needs to update that value needs to go through the ViewModel, e.g. through a function. This lets the ViewModel control the data, and how and when it gets changed.


    Hopefully that makes the difference between LiveData and MutableLiveData clearer - they're sort of two ways of looking at the same thing, it's just one is meant for the owner of the data (it needs to be able to update/mutate the stored value, that's the whole point!) and one is meant for consumers (they get a read-only view).

    So to update the value, and push it to observers, you **have to have a MutableLiveData and you have to call setValue() on it (or postValue if you need to). This is especially important when your LiveData holds a list, because mutating that list by changing its contents won't push an update to observers. As far as the LiveData is concerned, it's still holding the same value (a specific list object) and it has no idea if that's been changed. You have to explicitly update the value on the LiveData.


    So you have two options. One is you can update your list with add or whatever, and then call setValue with the same list object. That will push an update, because you've "changed the value" - this is true even if you haven't updated the list at all, you're just forcing an update to the LiveData.

    But that can cause problems, because the last value your observers got was the same list object. If you mutate it by changing the contents, everything that's storing that list as "the previous data" will see that change, because they're looking at the same list object. If you push that "new" data with setValue, observers will get the same list object they got last time. And if they need to compare them, they'll see they're exactly the same and "no change has happened", because they're comparing the same list object to itself.

    So it's usually a better idea to push a new list instead, so you're not interfering with the previous data. Meaning you should avoid using MutableList completely, and do this kind of thing instead:

    private val _numbers = MutableLiveData<List<Int>>()
    val numbers: LiveData = _numbers
    
    // when updating
    val newNumbers = (_numbers.value ?: emptyList()).plus(newNumber)
    _numbers.setValue(newNumbers)
    
    // or in a more Kotliny way
    _numbers.value = (_numbers.value ?: emptyList()) + newNumber
    

    Or if you're happy to have an empty list as your initial LiveData value (the above only provides observers with a list that has at least one Int in it):

    // now the LD contains a list as a default value
    private val _numbers = MutableLiveData<List<Int>>(emptyList())
    val numbers: LiveData = _numbers
    
    // updating - since we know the LD always contains a list, it can't be null
    _numbers.value = _numbers.value!! + newNumber
    

    So you're using plus to create a new list which is everything in the old one plus the new value. That way, the old list doesn't change, and anything that's hanging onto it won't get the data mysteriously changing behind its back.