Closed matejdro closed 7 years ago
You can use v7 DiffUtil https://developer.android.com/reference/android/support/v7/util/DiffUtil.html
Right, but this still throws away the information I have. For example If I add single item to a list in a ViewModel, there is no good way to tell the activity that. I still have to throw away that information, post entire new list and check what happened with DiffUtil.
I’d like to know this as well – I’m working with large (tenthousands of items) datasets, and need to handle their LiveData<List<...>> changing frequently. Just diffing them is a major waste of time. A LiveDataCollection type that can handle collections, and changes within them, would be a major improvement.
You can record the operations on the provider side, and dispatch the notifyItem... messages yourself, alongside the new list, something like: LiveData<Pair<List<>, MyListUpdate>>. You might find something useful in SortedList as well.
Alternately, if you're OK with diffing and just want it to be easier - ListAdapter is part of the new Paging library released in Architecture Components last week. It can manage diffing lists for you on a background thread, and can be easily bound to a LiveData<List<...>>
LiveData<List<Item>> myLiveDataList = ...;
myLiveDataList.observe(this, list -> listAdapter.setList(list));
There's also a PagedListAdapter, which does the same thing, but also handles updates within a arbitrarily large, lazy-loaded list. For PagedListAdapter specifically, it only diffs the items loaded in memory, and you can just simply call a getter for the item. All of the notifyItem... calls are handled for you.
Both of the adapters use DiffUtil to do the diffing work for you, but they can't be told when there's a specific known update - they'll always do a diff on all the loaded data. Doing that work on a background thread helps though, as does paging - if you have less of each dataset loaded when you're sending a new list, less diffing computation is needed.
I don't think any of the solutions mentioned above has related to the Original Question, I want this issue to be reopened as currently I find no way to update a single item in RecyclerView Adapter using LiveData inside ViewModel, we have to set entire list everytime which I don't think a better solution or technique.
+1 to reopening, I don't see a way to add, update or remove single item with a combination of PagedList
, PagedListAdapter
and PageKeyedDataSource
(for instance)
Simple case: Screen contains list of network fetched items. Element can be swipe-deleted. There is no way of telling any of the mentioned classes that it should just remove a single item from the list.
Same goes for adding or updating.
Since this is not an issue specifically related to any of the samples, please create an issue on the official issue tracker for Architecture Components.
Hey all, I had made an official feature request on this common operation - https://issuetracker.google.com/issues/77363656 :) If you like the feature request, please kindly stared it :)
If you are in control of adding each item to the list, then here is a simple solution to use an accompanying flag to notify the Activity. For example,
// inside ViewModel
val animalsList = ArrayList<Animal>()
val animalsListUpdated: MutableLiveData<Long> = MutableLiveData() // flag
// adding items to the list
fun updateList(item: Animal){
animalList.add(item)
animalListUpdated.value = System.currentTimeMillis() // updating the flag
}
// inside Activity
val adapter = Adapter(viewModel.animalsList)
// observing changes
viewModel.animalsListUpdated.observe(this, Observer { value ->
adapter.notifyDatasetChanged()
}
I feel like this is the worst solution. You should pass immutable lists through LiveData.
I feel like this is the worst solution. You should pass immutable lists through LiveData.
Don't worry, a small change will make the LiveData immutable in the above example:
private val _animalsListUpdated: MutableLiveData<Long> = MutableLiveData()
val animalsListUpdated: LiveData<Long> get() = _animalsListUpdated
Yes, but list itself is not immutable. You cannot for example update that list freely on the background thread without worying that Android might just be in the middle of the layout phase and drawing that list, causing concurrent modification exceptions.
Yes, but list itself is not immutable. You cannot for example update that list freely on the background thread without worying that Android might just be in the middle of the layout phase and drawing that list, causing concurrent modification exceptions.
If you are talking about someone outside of the ViewModel updating the list, we can provide an immutable list always. Applying the same logic as above,
private val _items: MutableList<Animal> = ArrayList()
val items: List<Animal> get() = _items
Is that what you meant?
No, I'm talking about updating inside viewmodel. This is the better way to do it if you really want to avoid LiveData:
var items: List<Animal> = emptyList()
private set
Note that list is not mutable. You can only swap out whole list, but not modify existing one. Like this:
animalList = animalList.toMutableList().apply { add(item) }
So is there any solution for this yet?
It does not look like it. I've just given up and switched to DiffUtils now. Yes, it is a bit less efficient, but it is much more convenient.
@matejdro Does your project have Rx in it? That would kind of solve everything because they have RxAdapter for exactly this kind of stuff. It's a bummer this isn't implemented. :(
Yes, but then I loose all benefits of LiveData (Lifecycle awareness).
@matejdro Yeah that's fair, although we do handle that manually by using Trello-lifecycle. 😄 Something I did the other day for this, which seemed to work is the following. I'm scanning for BLE devices and displaying them in a list with a custom adapter, so, whenever a new item is discovered, I have:
val newlyDiscoveredDevice = MutableLiveData<RxBleDevice>()
and I have the observer updating the data:
myClass.newlyDiscoveredDevice.observe(this, Observer {
deviceAdapter.add(it)
})
I don't know it something will leak, or if this is a really bad idea but..seems to be OK?
This is really offtopic, but I think it would be easier for you if you would collect devices in the data layer and push List<RxBleDevice>
through LiveData or Rx or whatever you use.
Yeah my bad, I didn't mean to throw the thread off-topic, just giving my two cents. I'll check that out! :D
Hey so maybe I am very late, but the best way to implement this seems to be using DiffUtil for a recyclerview.
2023 yet there's really no way other than using DiffUtils. :(
I think at this point you're better off using Kotlin Flows and Flow operators + lazy layouts in Jetpack Compose.
What is the best way to handle adapter updates with data arriving only through LiveData?
With everything in Activity, you could call all methods, such as
adapter.notifyItemAdded()
, but with LiveData you can only submit one type of item, generally whole list to display. That way you can only calladapter.notifyDataSetChanged()
which documentation states it should be used as last resort.So is there any better way to do it, or do we just need to call
adapter.notifyDataSetChanged()
from now on?