当LiveData列表中添加项时通知观察者

123

我需要在LiveData列表中添加项目时获得Observer事件。 但据我所知,只有在我用新列表替换旧列表时才会接收到该事件。 例如,当我执行以下操作时:

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

观察者收到了事件。但当我只是向值中添加项目时,观察者保持沉默。 请问你能给我建议如何实现我需要的功能吗?


3
它正在按照您所说的方式工作。您可以创建LiveData的子类和方法“add”,该方法将从旧列表创建新列表并附加新元素。 - bongo
@bongo 你是什么意思?当我向列表中添加一个项目时,它能工作吗? - Ruslan Leshchenko
我的意思是它的工作方式是“但据我所知,只有在我用新列表替换旧列表时,事件才会接收到”。 - bongo
@bongo 是的,我可以做到,但这样很浪费资源。有没有 Android SDK 的方法可以实现这个功能? - Ruslan Leshchenko
如果您想使用rx版本,请参考以下链接:https://dev59.com/G14b5IYBdhLWcg3wrzbY#61258643 - Erfan Eghterafi
10个回答

116

LiveData内部会使用版本号(一个简单的 int 类型计数器)跟踪每个更改。调用 setValue() 会递增该版本号,并仅在观察者的版本号小于 LiveData 的版本号时,更新任何观察者的新数据。

似乎唯一启动此过程的方法是调用 setValue() 或 postValue()。其副作用是,如果 LiveData 的基础数据结构发生了更改(例如将元素添加到 Collection 中),则不会向观察者传达这一点。

因此,在向列表添加项目后,您需要调用 setValue()。以下提供了两种方法来实现此目标。

选项1

将列表保存在 LiveData 外部,并在列表内容更改时使用引用进行更新。

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

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

选项2

通过LiveData跟踪列表的内容,并在列表内容发生更改时使用其自身的值更新LiveData。

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

init {
   mIssuePostLiveData.value = ArrayList()
}

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

无论哪种解决方案都可以帮助您避免每次修改当前列表时都必须创建一个新列表来通知观察者。

更新:

我已经使用类似的技术一段时间了,就像Gnzlt在他的回答中提到的那样,使用Kotlin扩展函数将LiveData分配给自身以简化代码。这本质上就是选项2的自动化 :) 我建议这样做。


1
我理解的是在这种情况下观察者每次都会收到新列表,对于仅订阅添加到列表中的新元素是否有可能呢? - NeverEndingQueue

114

我使用了 Kotlin 的 扩展函数 来使它更容易:

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

然后像这样在任何MutableLiveData中使用它:

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

25
更好的方式 fun <T> MutableLiveData<List<T>>.add(item: T) { val updatedItems = this.value?.toMutableList() ?: mutableListOf() updatedItems.add(item) this.value = updatedItems } - musooff
4
@musooff,就我的情况而言,这导致了崩溃。这就是为什么我在您的代码中做出了一些更改。我使用了MutableLiveData<MutableList<T>>代替了MutableLiveData<List<T>>。 - Aminul Haque Aome
3
Gnzlt 原始解决方案最佳。它更具通用性,因为它将数据类型与通知分开(关注点分离)。 - Mike F
7
如果您从后台线程使用notifyObserver(),应用程序将崩溃。 如果您需要从非 UI 线程发出通知,请使用this.postValue(this.value)。 - loshkin
+1 @toshkinl 我遇到了同样的问题,使用 postValue 而不是 this.value = this.value 解决了这个问题。 - Sanjeev Sharma
有人能确认它是否仍在工作吗?对我来说它不起作用。 - Juned Khatri

13

虽然有些晚,但这里给出一个更简洁的Gnzlt答案版本,包含空值检查,并适用于可变和不可变列表:

// 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)
}

在你的代码中:

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

你会如何通用地使用它? - Braian Coronel

10

LiveData 仅在其包装的对象引用发生变化时才会通知。当您将一个新的 List 分配给 LiveData 时,它将通知因为其包装的对象引用已经改变,但是如果从 LiveDataList 中添加/删除项目,则不会通知,因为它仍然具有相同的 List 引用作为包装对象。因此,您可以通过创建 MutableLiveData 的扩展来克服这个问题,如下所示:

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()
    }
}

然后像这样向您的MutableLiveData添加/删除项目:

// 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))

1
只有当引用被改变时,才是良好的观点。 - JackJack
这并不清楚是如何导致观察者在每次添加新值时得到通知的。除了第一次之外,对列表的引用始终相同。 - Johann
我们有没有办法从 viewModel 对象中公开 LiveData<List>(不可变列表)? - Ehsan Shadi
@AndroidDev 实际上,只有在将新值分配给“LiveData”变量时,观察者才会收到通知。 - Md. Yamin Mollah
@EhsanShadi 有很多方法可以从 ViewModel 获取 LiveData 的值:1. 您可以观察该 LiveData,2. 您可以在 ViewModel 或您的 Activity 中编写一个返回该 LiveData 值的函数,3. 您可以使用另一个 LiveData 发出该 LiveData 的值。如果您还不清楚,请提出描述您的问题并在评论部分提及我,我会在那里为您提供帮助。 - Md. Yamin Mollah

7

我为Kotlin找到了更好的解决方案:

class MutableListLiveData<T>(
        private val list: MutableList<T> = mutableListOf()
) : MutableList<T> by list, LiveData<List<T>>() {

    override fun add(element: T): Boolean =
            element.actionAndUpdate { list.add(it) }

    override fun add(index: Int, element: T) =
            list.add(index, element).also { updateValue() }

    override fun addAll(elements: Collection<T>): Boolean =
            elements.actionAndUpdate { list.addAll(elements) }

    override fun addAll(index: Int, elements: Collection<T>): Boolean =
            elements.actionAndUpdate { list.addAll(index, it) }

    override fun remove(element: T): Boolean =
            element.actionAndUpdate { list.remove(it) }

    override fun removeAt(index: Int): T =
            list.removeAt(index).also { updateValue() }

    override fun removeAll(elements: Collection<T>): Boolean =
            elements.actionAndUpdate { list.removeAll(it) }

    override fun retainAll(elements: Collection<T>): Boolean =
            elements.actionAndUpdate { list.retainAll(it) }

    override fun clear() =
            list.clear().also { updateValue() }

    override fun set(index: Int, element: T): T =
            list.set(index, element).also { updateValue() }

    private fun <T> T.actionAndUpdate(action: (item: T) -> Boolean): Boolean =
            action(this).applyIfTrue { updateValue() }

    private fun Boolean.applyIfTrue(action: () -> Unit): Boolean {
        takeIf { it }?.run { action() }
        return this
    }

    private fun updateValue() {
        value = list
    }
}

这种实现的优点是您可以在MutableListLiveData上使用Kotlin扩展函数。然后,您可以像这样使用它,您的LiveData将自动更新:
private val _items = MutableListLiveData<Item>()
val items: LiveData<List<Item>> = _items

fun addItem(item: Item) {
   _items.add(item)
}

如何在Java中使用这个? - Krutika Chotara

4
这个怎么样?
public class ListLiveData<T> extends LiveData<List<T>> {
    public void addAll(List<T> items) {
        if (getValue() != null && items != null) {
            getValue().addAll(items);
            setValue(getValue());
        }
    }

    public void clear() {
        if (getValue() != null) {
            getValue().clear();
            setValue(getValue());
        }
    }

    @Override public void setValue(List<T> value) {
        super.setValue(value);
    }

    @Nullable @Override public List<T> getValue() {
        return super.getValue();
    }
}

// add changed listener
    mMessageList.observe(mActivity, new Observer() {
        @Override public void onChanged(@Nullable Object o) {
            notifyDataSetChanged();
        }
    });

2
我遇到了相同的问题,决定在每个地方添加"value=value"或调用另一个方法太麻烦了,而且很容易出错。因此,我创建了自己的类来包装集合。观察者现在将观察集合内容,而不是集合本身。
该代码可在GitHub上找到: ObservableCollections 这是第一个版本,所以请原谅任何问题。它尚未可用于gradle包含,因此您将不得不下载它并将其添加为库模块到您的应用程序中。
目前,它包括以下集合:
ArrayDeque
ArrayList
LinkedList
Queue
Stack
Vector

更多内容将在时间允许的情况下添加。欢迎提出具体需求。 请务必报告任何问题。
编辑: 现在它包括更多的集合。有关详细信息,请参见那里。 此外,它可以通过gradle进行包含。同样,请参见那里以获取详细信息。
(Original Answer翻译成“最初的回答”)

1
我想分享我的解决方案(Java),我创建了一个自定义的MutableLiveData,记录了列表最后修改的时间,并相应地通知我们:
public class MutableListLiveData<T> extends MutableLiveData<List<T>> {
    private final MutableLiveData<Long> lastModified = new MutableLiveData<>();
    private List<T> items;
    private ListObserver<List<T>> callback;

    public MutableListLiveData() {
        this.items = new ArrayList<>();
    }

    public void addItem(T item) {
        items.add(item);
        onListModified();
    }

    public void removeItem(int position) {
        items.remove(position);
        onListModified();
    }

    public void updateItem(int position, T item) {
        items.set(position, item);
        onListModified();
    }

    public T getItem(int position) {
        return items.get(position);
    }

    private void onListModified() {
        lastModified.setValue(System.currentTimeMillis());
    }

    @Override
    public List<T> getValue() {
        return items;
    }

    @Override
    public void setValue(List<T> items) {
        this.items = items;
        onListModified();
    }

    public void observe(@NonNull LifecycleOwner owner, ListObserver<List<T>> callback) {
        this.callback = callback;
        lastModified.observe(owner, this::onListItemsChanged);
    }

    private void onListItemsChanged(long time) {
        if (callback != null) callback.onListItemsChanged(items, items.size());
    }

    public interface ListObserver<T> {
        void onListItemsChanged(T items, int size);
    }
}

使用方法:

MutableListLiveData<List<Integer>> myList = new MutableListLiveData<>();
myList.observe(owner, this::onListChanged(items, size);

private void onListChanged(List<Integer> items, int size) {
    // Do Something
}

0
受@user3682351的启发,这是我的解决方案。 似乎我们无法单独更新LiveData值的属性,因此必须在每个操作中更新该值。这本质上是一个小型包装器,具有修改HashMap属性的便捷方法。
import androidx.lifecycle.LiveData

/**
 * Hash Map Live Data
 *
 * Some convenience methods around live data for HashMaps. Putting a value on this will also update the entire live data
 * as well
 */
class HashMapLiveData<K, V> : LiveData<HashMap<K, V>>() {

    /**
     * Put a new value into this HashMap and update the value of this live data
     * @param k the key
     * @param v the value
     */
    fun put(k: K, v: V) {
        val oldData = value
        value = if (oldData == null) {
            hashMapOf(k to v)
        } else {
            oldData.put(k, v)
            oldData
        }
    }

    /**
     * Add the contents to the HashMap if there is one existing, otherwise set the value to this HashMap and update the
     * value of this live data
     * @param newData the HashMap of values to add
     */
    fun putAll(newData: HashMap<K, V>) {
        val oldData = value
        value = if (oldData != null) {
            oldData.putAll(newData)
            oldData
        } else {
            newData
        }
    }

    /**
     * Remove a key value pair from this HashMap and update the value of this live data
     * @param key the key to remove
     */
    fun remove(key: K) {
        val oldData = value
        if (oldData != null) {
            oldData.remove(key)
            value = oldData
        }
    }

    /**
     * Clear all data from the backing HashMap and update the value of this live data
     */
    fun clear() {
        val oldData = value
        if (oldData != null) {
            oldData.clear()
            value = oldData
        }
    }

    var value: HashMap<K, V>?
        set(value) = super.setValue(value)
        get() = super.getValue()

}


0
我认为这个课程会对你有所帮助:
class ArrayListLiveData<T> : MutableLiveData<ArrayList<T>>()
{
    private val mArrayList = ArrayList<T>()

    init
    {
        set(mArrayList)
    }

    fun add(value: T)
    {
        mArrayList.add(value)
        notifyChanged()
    }

    fun add(index: Int, value: T)
    {
        mArrayList.add(index, value)
        notifyChanged()
    }

    fun addAll(value: ArrayList<T>)
    {
        mArrayList.addAll(value)
        notifyChanged()
    }

    fun setItemAt(index: Int, value: T)
    {
        mArrayList[index] = value
        notifyChanged()
    }

    fun getItemAt(index: Int): T
    {
        return mArrayList[index]
    }

    fun indexOf(value: T): Int
    {
        return mArrayList.indexOf(value)
    }

    fun remove(value: T)
    {
        mArrayList.remove(value)
        notifyChanged()
    }

    fun removeAt(index: Int)
    {
        mArrayList.removeAt(index)
        notifyChanged()
    }

    fun clear()
    {
        mArrayList.clear()
        notifyChanged()
    }

    fun size(): Int
    {
        return mArrayList.size
    }
}

还有这些扩展:

fun <T> MutableLiveData<T>.set(value: T)
    {
        if(AppUtils.isOnMainThread())
        {
            setValue(value)
        }
        else
        {
            postValue(value)
        }
    }

    fun <T> MutableLiveData<T>.get() : T
    {
        return value!!
    }

    fun <T> MutableLiveData<T>.notifyChanged()
    {
        set(get())
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接