Уведомить наблюдателя, когда элемент добавлен в список 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())
}