Уведомить наблюдателя, когда элемент добавлен в список LiveData

Мне нужно получить событие Observer при добавлении элемента в Список LiveData. Но как я понимаю, событие получает только тогда, когда я заменяю старый список новым. Например, когда я делаю следующее:

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

Наблюдатель получает событие. Но когда я просто добавляю элемент к значению, Observer молчит. Не могли бы вы дать совет, как я могу реализовать то, что мне нужно?

10 ответов

Внутренне, LiveData отслеживает каждое изменение как номер версии (простой счетчик хранится как int). Вызов setValue() увеличивает эту версию и обновляет любые наблюдатели новыми данными (только если номер версии наблюдателя меньше, чем версия LiveData).

Похоже, что единственный способ запустить этот процесс - вызвать setValue() или postValue(). Побочным эффектом является то, что если базовая структура данных LiveData изменилась (например, добавление элемента в коллекцию), ничего не случится, чтобы сообщить об этом наблюдателям.

Таким образом, вам нужно будет вызвать 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<List<IssuePost>>()

init {
   mIssuePostLiveData.value = ArrayList()
}

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

Любое из этих решений должно помочь вам от необходимости создавать новый список каждый раз, когда вы изменяете текущий список, просто чтобы уведомить наблюдателей.

Я использую котлин Extension Function чтобы было проще:

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

Тогда используйте его в любом MutableLiveData как это:

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

Поздно к вечеринке, но вот более краткая версия ответа 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))

LiveDataбудет уведомлять только об изменении ссылки на обернутый объект. Когда вы назначаете новыйList к LiveData тогда он будет уведомлять, потому что ссылка на его обернутый объект изменена, но если добавить / удалить элементы из LiveDataс List он не будет уведомлять, потому что у него все еще есть 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))

Я нашел лучшее решение для Котлина:

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

Преимущество этой реализации в том, что вы можете использовать функции расширения Kotlin на MutableListLiveData. Затем вы можете использовать его так, и ваши LiveData автоматически обновятся:

      private val _items = MutableListLiveData<Item>()
val items: LiveData<List<Item>> = _items

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

Как насчет этого?

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

Я столкнулся с той же проблемой и решил, что добавление "value = value" или вызова другого метода повсюду было слишком болезненно и очень чревато ошибками.

Итак, я создал свои собственные классы, которые обертывают коллекции. Observe теперь будет наблюдать за содержимым коллекции, а не за самой коллекцией.

Код доступен на GitHub по адресу:https://github.com/theblitz/ObservableCollections

Это ПЕРВАЯ версия, поэтому, пожалуйста, извините за любые проблемы. Он еще не доступен для включения Gradle, поэтому вам придется загрузить его и добавить в качестве модуля библиотеки в свое приложение.

В настоящее время он включает следующие коллекции:

ArrayDeque
ArrayList
LinkedList
Queue
Stack
Vector

Другие будут добавлены, когда позволит время. Не стесняйтесь запрашивать конкретные.

И ПОЖАЛУЙСТА, сообщайте о любых проблемах.

Изменить: теперь он включает гораздо больше коллекций. Подробности смотрите там.
Кроме того, он доступен для включения через gradle. Опять же, подробности смотрите там.

Я придумал собственное решение (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
}

Вдохновленный @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()

}

Думаю, этот урок вам поможет:

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())
    }
Другие вопросы по тегам