Notify Observer when item is added to List of LiveData

I need to get an Observer event when the item is added to the List of LiveData. But as far as I understand the event receives only when I replace the old list with a new one. For example when I do the next:

list.value = mutableListOf(IssuePost(UserEntity(name, email, photoUrl), issueEntity))

Observer gets event. But when I just add item to value, Observer is silent. Could you please give me advice on how I can implement what I need?


Solution 1:

Internally, LiveData keeps track of each change as a version number (simple counter stored as an int). Calling setValue() increments this version and updates any observers with the new data (only if the observer's version number is less than the LiveData's version).

It appears the only way to start this process is by calling setValue() or postValue(). The side-effect is if the LiveData's underlying data structure has changed (such as adding an element to a Collection), nothing will happen to communicate this to the observers.

Thus, you will have to call setValue() after adding an item to your list. I have provided two ways you could approach this below.

Option 1

Keep the list outside of the LiveData and update with the reference any time the list contents change.

private val mIssuePosts = ArrayList<IssuePost>()
private val mIssuePostLiveData = MutableLiveData<List<IssuePost>>()

fun addIssuePost(issuePost: IssuePost) {
   mIssuePosts.add(issuePost)
   mIssuePostLiveData.value = mIssuePosts
}

Option 2

Keep track of the list via the LiveData and update the LiveData with its own value whenever the list contents change.

private val mIssuePostLiveData = MutableLiveData<MutableList<IssuePost>>()

init {
   mIssuePostLiveData.value = ArrayList()
}

fun addIssuePost(issuePost: IssuePost) {
    mIssuePostLiveData.value?.add(issuePost)
    mIssuePostLiveData.value = mIssuePostLiveData.value
}

Either of these solutions should help you from having to create a new list every time you modify the current list just to notify the observers.

UPDATE:

I've been using similar techniques for a while now as Gnzlt has mentioned in his answer to use a Kotlin extension function to assign the LiveData to itself to simplify the code. This is essentially Option 2 automated :) I would recommend doing that.

Solution 2:

I use a Kotlin Extension Function to make it easier:

fun <T> MutableLiveData<T>.notifyObserver() {
    this.value = this.value
}

Then use it in any MutableLiveData like this:

fun addIssuePost(issuePost: IssuePost) {
    mIssuePostLiveData.value?.add(issuePost)
    mIssuePostLiveData.notifyObserver()
}

Solution 3:

Late to the party but here is a more concise version of Gnzlt's answer with null-check and for both mutable and immutable lists:

// for mutable list
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(item: T) {
    val value = this.value ?: mutableListOf()
    value.add(item)
    this.value = value
}

// for immutable list
operator fun <T> MutableLiveData<List<T>>.plusAssign(item: T) {
    val value = this.value ?: emptyList()
    this.value = value + listOf(item)
}

And in your code:

list += IssuePost(UserEntity(name, email, photoUrl), issueEntity))

Solution 4:

LiveData will only notify when its wrapped object reference is changed. When you assign a new List to a LiveData then it will notify because its wrapped object reference is changed but if add/remove items from a LiveData's List it will not notify because it still has the same List reference as wrapped object. So you can overcome this problem by making an extension of MutableLiveData as follows:

fun <T> MutableLiveData<MutableList<T>>.addNewItem(item: T) {
    val oldValue = this.value ?: mutableListOf()
    oldValue.add(item)
    this.value = oldValue
}

fun <T> MutableLiveData<MutableList<T>>.addNewItemAt(index: Int, item: T) {
    val oldValue = this.value ?: mutableListOf()
    oldValue.add(index, item)
    this.value = oldValue
}

fun <T> MutableLiveData<MutableList<T>>.removeItemAt(index: Int) {
    if (!this.value.isNullOrEmpty()) {
        val oldValue = this.value
        oldValue?.removeAt(index)
        this.value = oldValue
    } else {
        this.value = mutableListOf()
    }
}

Then add/remove items from your MutableLiveData like:

// Here is your IssuePost list
var issuePostList = MutableLiveData<MutableList<IssuePost>>()

// Add new item to your list
issuePostList.addNewItem(IssuePost(UserEntity(name, email, photoUrl), issueEntity))

// Delete an item from your list at position i
issuePostList.removeItemAt(i)

// Add new item to your list at position i
issuePostList.addNewItemAt(i, IssuePost(UserEntity(name, email, photoUrl), issueEntity))