I have wrote a MutableLiveList
class for android, similar to existing MutableLiveData
, but this class instead handles a list and notifies its observers on structural changes like adding items and removing items. It is intended to be used inside viewModels similar to MutableLiveData
. Class implementation is like this:
class MutableLiveList<E> : MutableList<E> {
private val list = mutableListOf<E>()
private val listChange = MutableLiveData<Triple<Int, Int, ListEvent>>()
fun observe(owner: LifecycleOwner, observer: ListObserver) {
//newly added observer must not listen to old changeEvents
/*EDIT*/listChange.value = Triple(-1, 0, NONE)
listChange.observe(owner, {
when (it.third) {
/*EDIT*/ NONE -> {}
ADD -> observer.onAdded(it.first, it.second)
REMOVE -> observer.onRemoved(it.first, it.second)
}
})
}
//List methods : delegate to corresponding list.methods
override val size get() = list.size
override fun contains(element: E) = list.contains(element)
override fun containsAll(elements: Collection<E>) = list.containsAll(elements)
override fun get(index: Int) = list[index]
override fun indexOf(element: E) = list.indexOf(element)
override fun lastIndexOf(element: E) = list.lastIndexOf(element)
override fun isEmpty() = list.isEmpty()
override fun iterator() = list.iterator()
override fun listIterator() = list.listIterator()
override fun listIterator(index: Int) = list.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int) = list.subList(fromIndex, toIndex)
//MutableList methods : trigger list observers.
override fun add(element: E) = add(size, element).run { true }
override fun add(index: Int, element: E) = list.add(index, element).also {
listChange.value = Triple(index, 1, ADD)
}
override fun addAll(elements: Collection<E>) = addAll(size, elements)
override fun addAll(index: Int, elements: Collection<E>) = list.addAll(index, elements).also {
listChange.value = Triple(index, elements.size, ADD)
}
override fun clear() {
val size = size
list.clear()
listChange.value = Triple(0, size, REMOVE)
}
override fun remove(element: E): Boolean {
val index = list.indexOf(element)
if (index == -1)
return false
removeAt(index)
return true
}
override fun removeAt(index: Int) = list.removeAt(index).also {
listChange.value = Triple(index, 1, REMOVE)
}
override fun retainAll(elements: Collection<E>): Boolean {
var modified = false
for (ele in list.toList()) {
if (ele !in elements) {
remove(ele)
modified = true
}
}
return modified
}
override fun removeAll(elements: Collection<E>): Boolean {
var modified = false
for (ele in list.toList()) {
if (ele in elements) {
remove(ele)
modified = true
}
}
return modified
}
override fun set(index: Int, element: E) = list.set(index, element).also {
listChange.value = Triple(index, 1, REMOVE)
listChange.value = Triple(index, 1, ADD)
}
}
interface ListObserver {
fun onAdded(start: Int, size: Int)
fun onRemoved(start: Int, size: Int)
}
private enum class ListEvent {
NONE, ADD, REMOVE;
}
Example usage:
val userList = MutableLiveList<User>()
.
.
.
userListAdapter = UserListAdapter(userList)
userList.observe(this, object : ListObserver {
override fun onAdded(start: Int, size: Int) {
userListAdapter.notifyItemRangeInserted(start, size)
}
override fun onRemoved(start: Int, size: Int) {
userListAdapter.notifyItemRangeRemoved(start, size)
}
})
This is working fine for now in main thread. I suspect would this work on background thread or not! Is this good enough? Are there any lurking bugs?
EDIT: Added two lines in the code. I don't know if these two lines are necessarily required (I tried with and without, both worked well). Without this line, whenever a new observer is added, it will get a redundant onAdded
/onRemoved
event depending on the recent change made to the list, before attaching the observer.