将项目添加到 LiveData 列表时通知观察者

Posted

技术标签:

【中文标题】将项目添加到 LiveData 列表时通知观察者【英文标题】:Notify Observer when item is added to List of LiveData 【发布时间】:2018-06-05 02:37:27 【问题描述】:

当项目添加到 LiveData 列表时,我需要获取一个观察者事件。但据我了解,只有当我用新列表替换旧列表时,事件才会收到。 例如当我做下一个时:

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

观察者得到事件。但是当我只是将 item 添加到 value 时,Observer 是沉默的。 您能否就如何实现我需要的东西给我建议?

【问题讨论】:

如你所说。您可以创建 LiveData 的子类和方法“add”,该方法将从旧列表创建新列表并附加新元素。 @bongo 你什么意思?当我将一个项目添加到列表中时它是否有效? 我的意思是它的工作原理是“但据我所知,事件只有在我用新列表替换旧列表时才会收到” @bongo 是的,我可以,但这很浪费。 android SDK 有没有办法做到这一点? 如果你想使用 rx 版本***.com/a/61258643/4718406 【参考方案1】:

在内部,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 自动化 :) 我建议这样做。

【讨论】:

【参考方案2】:

我使用 Kotlin Extension Function 使其更容易:

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

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

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

【讨论】:

更好的方法fun &lt;T&gt; MutableLiveData&lt;List&lt;T&gt;&gt;.add(item: T) val updatedItems = this.value as ArrayList updatedItems.add(item) this.value = updatedItems @musooff,就我而言,这导致了崩溃。这就是为什么,我在你的代码中改变了一点。我使用 MutableLiveData> 而不是 MutableLiveData> Gnzlt 的原始解决方案是最好的。它更通用,因为它将数据类型与通知 (separation of concerns) 分开。 如果你从后台线程使用 notifyObserver() 应用程序将会崩溃。如果您从非 UI 线程通知,请使用 this.postValue(this.value)。 这个答案救了我的命【参考方案3】:

迟到了,但这里有一个更简洁的 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))

【讨论】:

一般情况下你会如何使用它?【参考方案4】:

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

【讨论】:

好点 * 仅当参考改变时 * 尚不清楚这如何导致观察者在每次添加新值时收到通知。对列表的引用(第一次除外)始终相同。 无论如何如果我们想从我们的 viewModel 对象中公开 LiveData(不可变列表)? @AndroidDev 实际上,观察者只有在为LiveData 变量分配新值时才会收到通知。 @EhsanShadi 有很多方法可以获取LiveData' from ViewModel` 的值 1. 你可以观察到LiveData, 2. 你可以在ViewModel 或者你的Activity 返回那个LiveData 的值,3. 你可以通过使用另一个LiveData 来发出那个LiveData 的值。如果你还不清楚!那么请提出一个描述您的问题的问题,并在评论部分提及我,我会在那里为您提供帮助。【参考方案5】:

我为 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中使用这个?【参考方案6】:

这个怎么样?

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

【讨论】:

【参考方案7】:

我遇到了同样的问题,并决定到处添加“value = value”或调用另一个方法太痛苦了,而且很容易出错。

所以,我创建了自己的类来包装集合。 Observe 现在将观察集合的内容,而不是集合本身。

代码可在 GitHub 上获取: ObservableCollections

这是第一个版本,所以请原谅任何问题。 它还不能用于 gradle include,因此您必须下载它并将其作为库模块添加到您的应用程序中。

目前包括以下合集:

ArrayDeque
ArrayList
LinkedList
Queue
Stack
Vector

如果时间允许,将添加更多内容。 随意请求特定的。

请报告任何问题。

编辑: 它现在包括更多的收藏。有关详细信息,请参见此处。 此外,它还可以通过 gradle 包含在内。再次,请参阅此处了解详细信息。

【讨论】:

【参考方案8】:

受@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()



【讨论】:

【参考方案9】:

我认为这门课会对你有所帮助:

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

【讨论】:

【参考方案10】:

我想出了自己的解决方案 (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

【讨论】:

以上是关于将项目添加到 LiveData 列表时通知观察者的主要内容,如果未能解决你的问题,请参考以下文章

LiveData 未通知我的服务(LifecyclerOwner 实施)观察者

LiveData原理分析

Android Jetpack ------ LiveData的使用

Android Jetpack ------ LiveData的使用

Android Jetpack ------ LiveData的使用

Android-LiveData原理解析