I'm using LiveData and ViewModel from the architecture components in my app.
I have a list of items that is paginated, I load more as the user scrolls down. The result of the query is stored in a
MutableLiveData<List<SearchResult>>
When I do the initial load and set the variable to a new list, it triggers a callback on the binding adapter that loads the data into the recyclerview.
However, when I load the 2nd page and I add the additional items to the list, the callback is not triggered. However, if I replace the list with a new list containing both the old and new items, the callback triggers.
Is it possible to have LiveData notify its observers when the backing list is updated, not only when the LiveData object is updated?
This does not work (ignoring the null checks):
val results = MutableLiveData<MutableList<SearchResult>>()
/* later */
results.value.addAll(newResults)
This works:
val results = MutableLiveData<MutableList<SearchResult>>()
/* later */
val list = mutableListOf<SearchResult>()
list.addAll(results.value)
list.addAll(newResults)
results.value = list
I think the extension is a bit nicer.
operator fun <T> MutableLiveData<ArrayList<T>>.plusAssign(values: List<T>) {
val value = this.value ?: arrayListOf()
value.addAll(values)
this.value = value
}
Usage:
list += anotherList;
According to MutableLiveData, you need to use postValue or setValue in order to trigger the observers.
Related
I want to add an item to a List of MutableLiveData in ViewModel.
List is read-only because it was initialized with listOf().
But in a specific case I want to add an item.
To do this, I used toMutableList() to type cast, but as a result of debugging, there was no change in the List of LiveData.
How can I apply data to a List of LiveData?
class WriteRoutineViewModel : ViewModel() {
private var _items: MutableLiveData<List<RoutineModel>> = MutableLiveData(listOf())
val items: LiveData<List<RoutineModel>> = _items
fun addRoutine(workout: String) {
val item = RoutineModel(workout, "TEST")
_items.value?.toMutableList()?.add(item) // nothing change
}
}
_items.value?.toMutableList() creates a new list instance, so you're not adding elements to the list in the LiveData.
Even if you did manage to add elements to the actual list object in the LiveData (by using some dirty cast), it would probably not trigger a change event in the LiveData, so subscribers wouldn't be notified of the new value.
What you want is to actually assign a new list to the MutableLiveData:
_items.value = _items.value?.plus(item) ?: listOf(item)
(if the current list in the LiveData is null, a new list will be assigned with just the new item)
I simply want to add a Node to a List when the user clicks a button and display it in a composable LazyColumn.
Here is how I thought it would work:
VieModel:
private val _nodeList: MutableLiveData<MutableList<Node>> = MutableLiveData()
val nodeList: LiveData<MutableList<Node>> = _nodeList
fun onNodeListChange(newNode: Node){
_nodeList.value?.add(newNode)
}
and in my Composable I try to observe it by calling:
val vm = getViewModel<MainViewModel>()
val nodeList: List<Node> by vm.nodeList.observeAsState(listOf())
In the onClick of the Button I call:
val newNode = Node(title, body)
vm.onNodeListChange(newNode)
The node gets added to the nodeList in the ViewModel but the Composable wont recompose.
What am I doing wrong?
Your LiveData is not changing. You are just adding a new item to the list stored within the LiveData. For a LiveData to notify it's observers, the object returned by livedata.getValue() must occupy a different memory space from the one supplied through livedata.setValue() One quick and dirty solution would be:
_nodeList.value = _nodeList?.value?.toMutableList()?.apply{add(newNode)}
In Jetpack compose you probably want to use a SnapShotStateList for an observable list instead of a LiveData<List<T>>
I want to get 2 different entities as 1 LiveData List
so that I could show them in a RecyclerView
and update it on data changes
If I understand correctly, you have 2 room entities, both with one value, in it's own dao.
You want to combine those to a list (with up to 2 items) that can be observed as liveData.
I would go with a MediatorLiveData, get the entities as liveData from Room, and combine them like this:
class SomeClass {
private val list1: LiveData<Something> = dao1.getLiveSomething()
private val list2: LiveData<Something> = dao2.getOtherLiveSomething()
private val _combinedList = MediatorLiveData<List<Something>>().apply{
addSource(list1){
value = listOfNotNull(it, list2.value)
}
addSource(list2){
value = listOfNotNull(list1.value, it)
}
}
/**
* Observe this for combined list
*/
val combinedList: LiveData<List<Something>>
get() = _combinedList
}
This way, when one of the entities in Room get changed, combinedList will send the new data to any observers.
I am using one of Android Jetpack Component ViewModel + Live data in my project it works fine for me when using normal data such as string and Int but when it comes to arrayList it won't observe anything
Here's my code
class MainActivityModel : ViewModel() {
private var dataObservable = MutableLiveData<ArrayList<Int>>()
init {
dataObservable.value = arrayListOf(1,2,3,4,5)
}
fun getInt(): LiveData<ArrayList<Int>> = dataObservable
fun addInt(i:Int) {
dataObservable.value!![i] = dataObservable.value!![i].plus(1)
}
}
A LiveData won't broadcast updates to observers unless its value is completely reassigned with a new value. This does not reassign the value:
dataObservable.value!![i] = dataObservable.value!![i].plus(1)
What it does is retain the existing array, but add an array element. LiveData doesn't notify its observables about that. The actual array object has to be reassigned.
If you want to reassign a new array value and notify all observers, reassign a new array altogether, like this:
dataObservable.value = dataObservable.value!![i].plus(1)
Assigning dataObservable.value will call the LiveData setValue() method, and notify observers of the new value passed to setValue() .
If you modify your complex observable object outside of main thread you need to use postValue
dataObservable?.value[i] += 1
dataObservable.apply {
postValue(value)
}
Currently I am using Android Architecture Components for App development everything is working along with paging library Now I want to remove recyclerview Item using PagedListAdapter to populate this we required to add a data source and from data source list is updating using LiveData no I want to remove a item from list notifyItemRemoved() is working from PagedList I am getting this exception:
java.lang.UnsupportedOperationException
java.util.AbstractList.remove(AbstractList.java:638)
Since you cannot directly modify the data that you have in the PagedList as I believe its immutable, the key to implementing the removal of items in this situation is maintaining a backing dataset somewhere in your code that represents all the data that you have received so far. An ArrayList worked for me.
When you want to remove an item, remove it from your backing dataset and call invalidate on your data source. This will trigger the loadInitial() call, in which you can pass your backing dataset to the callback.onResult() function. Your PagedListAdapter will use the DiffUtil.ItemCallback you supplied to smartly determine that there has been an update to its data and will update itself accordingly.
This tutorial helped me understand the correct flow to use in order to properly delete an item while using the paging library:
https://medium.com/#FizzyInTheHall/implementing-swipe-to-delete-with-android-architecture-components-a95165b6c9bd
The repo associated with the tutorial can be found here:
https://gitlab.com/adrianhall/notes-app
Temporary hide item in list by call notifyItemUpdated() base on flags set in Pojo object
if(data?.hasVisible == false) {
itemBinding.root.visibility = View.GONE
itemBinding.root.layoutParams = RecyclerView.LayoutParams(0, 0)
}else{
itemBinding.root.visibility = View.VISIBLE
itemBinding.root.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT)
}
I had same issue.
My PagedList displayed items that DataSource factory fetched from server. I came up with two solutions how to remove item from list.
Reload whole list.
Make API call to remove item from server and then call
pagedList.dataSource.invalidate().
Downside of this solution is that whole list is cleared and then all items received from server. Not exactly what I was looking for.
Store results in Room. Then PagedList will get items directly from Room and PagedListAdapter will manage removing/adding items itself.
In DAO object
("SELECT * FROM YourModel")
fun getAll(): DataSource.Factory<Int, YourModel>
#Delete
fun delete(item: YourModel)
To update database as user scrolls list I implemented BoundaryCallback. It is being called when there are no more items to show in DB, it can be called at the end of same page, so I ensured to not execute same request few times (In my case list's key is page number).
class YourModelBoundaryCallback(private val repository: Repository) : PagedList.BoundaryCallback<YourModel>() {
private val requestArray = SparseArray<Disposable>()
private var nextPage = 1
private var lastPage = 1
override fun onZeroItemsLoaded() {
if (requestArray.get(1) == null) {
requestArray.put(1, repository.getFirstPage()
.subscribe({
lastPage = it.total / PAGE_SIZE + if (it.total % PAGE_SIZE == 0) 0 else 1
nextPage++
}))
}
}
override fun onItemAtEndLoaded(itemAtEnd: YourModel) {
if (nextPage > lastPage) {
return
}
if (requestArray.get(nextPage) == null) {
requestArray.put(nextPage, repository.getNextPage(nextPage)
.subscribe({
lastPage = it.total / PAGE_SIZE + if (it.total % PAGE_SIZE == 0) 0 else 1
nextPage++
}))
}
}
override fun onItemAtFrontLoaded(itemAtFront: YourModel) {
// ignored, since we only ever append to what's in the DB
}
}
PagedList instance became this
private val pagedListConfig = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(3)
.setPageSize(PAGE_SIZE)
.build()
val pagedList = LivePagedListBuilder(yourModelDao.getAll(), pagedListConfig)
.setBoundaryCallback(YourModelBoundaryCallback(repository))
.build()
And finally to remove item from adapter I just call yourModelDao.remove(yourModel)
Adapter.notifyItemRemoved(position);
appDatabase.userDao().deleteUser(user);
It's work for me!