ListAdapter not updating item in RecyclerView - android

I'm using the new support library ListAdapter. Here's my code for the adapter
class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(parent.inflate(R.layout.item_artist))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(artist: Artist) {
itemView.artistDetails.text = artist.artistAlbums
.plus(" Albums")
.plus(" \u2022 ")
.plus(artist.artistTracks)
.plus(" Tracks")
itemView.artistName.text = artist.artistCover
itemView.artistCoverImage.loadURL(artist.artistCover)
}
}
}
I'm updating the adapter with
musicViewModel.getAllArtists().observe(this, Observer {
it?.let {
artistAdapter.submitList(it)
}
})
My diff class
class ArtistsDiff : DiffUtil.ItemCallback<Artist>() {
override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
return oldItem?.artistId == newItem?.artistId
}
override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
return oldItem == newItem
}
}
What's happening is when submitList is called the first time the adapter renders all the items, but when submitList is called again with updated object properties it does not re-render the view which has changed.
It re-renders the view as I scroll the list, which in turn calls bindView()
Also, I've noticed that calling adapter.notifyDatasSetChanged() after submit list renders the view with updated values, but I don't want to call notifyDataSetChanged() because the list adapter has diff utils built-in
Can anyone help me here?

Edit: I understand why this happens that wasn't my point. My point is that it at least needs to give a warning or call the notifyDataSetChanged() function. Because apparently I am calling the submitList(...) function for a reason. I am pretty sure people are trying to figure out what went wrong for hours until they figure out the submitList() ignores silently the call.
This is because of Googles weird logic. So if you pass the same list to the adapter it does not even call the DiffUtil.
public void submitList(final List<T> newList) {
if (newList == mList) {
// nothing to do
return;
}
....
}
I really don't understand the whole point of this ListAdapter if it can't handle changes on the same list. If you want to change the items on the list you pass to the ListAdapter and see the changes then either you need to create a deep copy of the list or you need to use regular RecyclerView with your own DiffUtill class.

The library assumes you are using Room or any other ORM which offers a new async list every time it gets updated, so just calling submitList on it will work, and for sloppy developers, it prevents doing the calculations twice if the same list is called.
The accepted answer is correct, it offers the explanation but not the solution.
What you can do in case you're not using any such libraries is:
submitList(null);
submitList(myList);
Another solution would be to override submitList (which doesn't cause that quick blink) as such:
#Override
public void submitList(final List<Author> list) {
super.submitList(list != null ? new ArrayList<>(list) : null);
}
Or with Kotlin code:
override fun submitList(list: List<CatItem>?) {
super.submitList(list?.let { ArrayList(it) })
}
Questionable logic but works perfectly.
My preferred method is the second one because it doesn't cause each row to get an onBind call.

with Kotlin just you need to convert your list to new MutableList like this or another type of list according to your usage
.observe(this, Observer {
adapter.submitList(it?.toMutableList())
})

I had a similar problem but the incorrect rendering was caused by a combination of setHasFixedSize(true) and android:layout_height="wrap_content". For the first time, the adapter was supplied with an empty list so the height never got updated and was 0. Anyway, this resolved my issue. Someone else might have the same problem and will think it is problem with the adapter.

If you encounter some issues when using
recycler_view.setHasFixedSize(true)
you should definitly check this comment:
https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531
It solved the issue on my side.
(Here is a screenshot of the comment as requested)

According to the official docs :
Whenever you call submitList it submits a new list to be diffed and displayed. This is why whenever you call submitList on the previous (already submitted list), it does not calculate the Diff and does not notify the adapter for change in the dataset.

Wasted so much time to figure out the problem in same case.
But in my situation the problem was that i forgot to specify a layoutManager for my recyclerView: vRecyclerView.layoutManager = LinearLayoutManager(requireContext())
I hope no one will repeat my mistake...

Today I also stumbled upon this "problem".
With the help of insa_c's answer and RJFares's solution I made myself a Kotlin extension function:
/**
* Update the [RecyclerView]'s [ListAdapter] with the provided list of items.
*
* Originally, [ListAdapter] will not update the view if the provided list is the same as
* currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T>
* could never work - the [ListAdapter] must have the previous list if items to compare new
* ones to using provided diff callback.
* However, it's very convenient to call [ListAdapter.submitList] with the same list and expect
* the view to be updated. This extension function handles this case by making a copy of the
* list if the provided list is the same instance as currently loaded one.
*
* For more info see 'RJFares' and 'insa_c' answers on
* https://stackoverflow.com/questions/49726385/listadapter-not-updating-item-in-reyclerview
*/
fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) {
// ListAdapter<>.submitList() contains (stripped):
// if (newList == mList) {
// // nothing to do
// return;
// }
this.submitList(if (list == this.currentList) list.toList() else list)
}
which can then be used anywhere, e.g.:
viewModel.foundDevices.observe(this, Observer {
binding.recyclerViewDevices.adapter.updateList(it)
})
and it only (and always) copies the list if it is the same as currently loaded one.

In my case I forgot to set the LayoutManager for the RecyclerView. The effect of that is the same as described above.

I got some strange behavior. I'm using MutableList in LiveDate.
In kotlin, the following codes don't work:
mViewModel.products.observe(viewLifecycleOwner, {
mAdapter.submitList(it)
})
But, when I change it to it.toList(), it works
mViewModel.products.observe(viewLifecycleOwner, {
mAdapter.submitList(it.toList())
})
Although, "it" was the same list.

For me, this issue appeared if I was using RecyclerView inside of ScrollView with nestedScrollingEnabled="false" and RV height set to wrap_content.
The adapter updated properly and the bind function was called, but the items were not shown - the RecyclerView was stuck at its' original size.
Changing ScrollView to NestedScrollView fixed the issue.

I had a similar problem. The issue was in the Diff functions, which didn't adequately compare the items. Anyone with this issue, make sure your Diff functions (and by extension your data object classes) contain proper comparison definitions - i.e. comparing all fields which might be updated in the new item. For example in the original post
override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
return oldItem == newItem
}
This function (potentially) does not do what it says on the label: it does not compare the contents of the two items - unless you have overridden the equals() function in the Artist class. In my case, I had not, and the definition of areContentsTheSame only checked one of the necessary fields, due to my oversight when implementing it. This is structural equality vs. referential equality, you can find more about it here

The reason your ListAdapter .submitlist is not called is because the object
you updated still holds the same adress in memory.
When you update an object with lets say .setText it changes the value in the original object.
So that when you check if object.id == object2.id it will return as the same
because the both have a reference to the same location in memory.
The solution is to create a new object with the updated data and insert that in your list. Then submitList will be called and it will work correctly

It solve my problem. I think the best way is not to override submitList but add a new function to add new list.
fun updateList(list: MutableList<ScaleDispBlock>?) {
list?.let {
val newList = ArrayList<ScaleDispBlock>(list)
submitList(newList)
}
}

I also ran into similar issue, my usecase was i had a clickHandler and item will be selected/not selected (toggle on click).
I tried most of the approach from the above answers, only thing that worked is
adapter.submitList(null)
adapter.submitList(modifiedList)
but problem with this is everytime i click on any clickHandler the whole list is being redrawn again which is very ineffecient.
What i did ?
I made a live data that will store last clicked item and observing that live data, we can tell adapter that live data has been updated like below
viewModel.lastClicked.observe(viewLifeCycleOwner, {
adapter.notifyItemChanged(it)
}

Had a VERY similar issue, to this one, and decided to open a new thread and even create a GitHub project to mess around with. Most solutions didn't quite work for me, not even the toMutableList() way. In my case, the problem was solved by using immutable classes and submitting immutable Lists to the Adapter.

For anyone who's scenario is same as mine, I leave my solution, which I don't know why it's working, here.
The solution which worked for me was from #Mina Samir, which is submitting the list as a mutable list.
My Issue scenario :
-Loading a friend list inside a fragment.
ActivityMain attaches the FragmentFriendList(Observes to the livedata of friend db items) and on the same time, requests a http request to the server to get all of my friend list.
Update or insert the items from the http server.
Every change ignites the onChanged callback of the livedata. But, when it's my first time launching the application, which means that there was nothing on my table, the submitList succeeds without any error of any kind, but nothing appears on the screen.
However, when it's my second time launching the application, data are being loaded to the screen.
The solution is, as metioned above, submitting the list as a mutableList.

As has already been mentioned, you cannot submit a List with the same reference because the ListAdapter will see the lists are in the same location and will therefore not be able to use the DiffUtil.
The simplest solution would be to make a shallow copy of the list.
submitList(ArrayList(list))
Be wary converting the List to a MutableList, as that can create conditions for Exceptions and hard to find bugs.

this will work ....
what happen Is when you get the current list you are pointing to the same list at same location

I needed to modify my DiffUtils
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
To actually return whether the contents are new, not just compare the id of the model.

Using #RJFares first answer updates the list successfully, but doesn't maintain the scroll state. The entire RecyclerView starts from 0th position. As a workaround, this is what I did:
fun updateDataList(newList:List<String>){ //new list from DB or Network
val tempList = dataList.toMutableList() // dataList is the old list
tempList.addAll(newList)
listAdapter.submitList(tempList) // Recyclerview Adapter Instance
dataList = tempList
}
This way, I'm able to maintain the scroll state of RecyclerView along with modified data.

Optimal Soltion:
for Kotlin
var list :ArrayList<BaseModel> = ArrayList(adapter.currentList)
list.add(Item("Content"))
adapter.submitList(list) {
Log.e("ListAdaptor","List Updated Successfully")
}
We should not maintain another base list as adapter.currentList will return a list in which diff is already calculated.
We have to provide a new instance every time a list updated because of DiffUtil
As per android documentation
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
One list is already maintained by AsyncListDiffer which runs the diffutil on the background thread and another one has to be passed using adaptor.submitList()

The way that worked for me is to override the submitList() and create a copy of the incoming list and each item inside it too:
override fun submitList(list: List<Item>?) {
val listCopy =
mutableListOf<Item>().apply {
list?.map {
add(Item(it.id, it.name, it.imageUrl))
}
}
super.submitList(listCopy)
}

I encounter a very similar issue.
After the data list changed, I submit it again, the recycler view doesn't show as I wanted. It shows duplicated items.
I haven't found the root cause, but I find a workaround, that is to set the adapter to recycler view again. I guess this makes recycler viewer forget the memory before and render again correctly.
userNftListFiltered = SOME_NEW_VALUE
binding.nftSendSearchList.adapter = searchNftAdapter //set adapter again
searchNftAdapter.submitList(userNftListFiltered)

Once you have modify the array list, you have to let adapter know that which position that should be change
this code below is working in my case wish it may help
private fun addItem() {
val index = myArrayList.size
val position = myArrayList.size+1
myArrayList.add(
index, MyArrayClass("1", "Item Name")
)
myAdapter.notifyItemInserted(position) // in case of insert
// in case of remove item
// val index = myArrayList.size-1
// myAdapter.notifyItemRemoved(index)
}

just call adapter.notifyDataSetChanged() after differ.submitList

In my case i was using same object(from adadptar) to update Room database.
Create new object to update database and it'll fix the issue.
Example: I was doing this ->
val playlist = adapter.getItem(position)
playlist.name = "new name"
updatePlaylistObjectInRoomDatabase(playlist)
above code will change object in adapter before room database. So no change will be detected by DiffUtil callback.
Now doing this ->
val playlist = adapter.getItem(position)
val newPlaylist = Playlist()
newPlaylist.id = playlist.id
newPlaylist.name = "new name"
updatePlaylistObjectInRoomDatabase(newPlaylist)
Above code will not change anything in adapter list and will only change data in room database. so submitList will have different values DiffUtil callback can detect.
Enjoy the little things :)

This is something naturally expecte to be available on the official API, but as it isn't, this can be a way to deal with it:
fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.clearItems() {
submitList(null)
submitList(emptyList())
}

The adapter can not understand that you have some updates, I don't know why!?
I am adding some entities to the list ad I m expected to collect them at the consumption point. But, nothing happens.
As a solution that worked for me you can use the script below:
artistAdapter.submitList(it.toMutableList())

Because the problem lays inside the ListAdapter, I would like to solve it inside the ListAdapter.
Thanks to Kotlin extension, we can write it like:
class MyItemAdapter() :
ListAdapter<Item, RecyclerView.ViewHolder>(ItemDiffCallback) {
// ...
override fun submitList(list: List<Item>?) {
super.submitList(list?.toList())
}
}
It does look like a tricky hack. So I'd like to make a comment too:
super.submitList(list?.toList()) // to make submitList work, new value MUST be a new list. https://stackoverflow.com/a/50031492/9735961
And yes, thank you, RecyclerView developers.

Related

How to replace adapter list of data without refreshing the recyclerview

i have recyclerView in my fragment and i want to change the list of data in the recyclerView adapter without refreshing the recyclerView
i am using this two functions like that
mAdapter.clearList()
mAdapter.addItems(newList)
but there is a quick refresh because of clearList() function
anyone have better function to use
fun addItems(items: List<T>) {
val myList = adapterItems()
var count = myList.size
// Remove loading indicator dummy item
if (count > 0 && hasMore()) {
count--
myList.removeAt(count)
notifyItemRemoved(count)
}
// Insert extra data
myList.addAll(items)
notifyItemRangeInserted(count, items.size)
}
fun clearList() {
val myList = adapterItems()
val count = myList.size
myList.clear()
notifyItemRangeRemoved(0, count)
}
You could make a setItems function that clears and then adds before notifying the RecyclerView with notifyDataSetChanged() - I'm not sure if that would matter though, I feel like it should all be resolved before you get a layout pass and the screen updates. A glitch you can see feels like one thing happens and then another later - I'm not sure how it's all handled internally though, if these things are queued up or not. Worth a try!
Another thing you could try is using notifyDataSetChanged() in clearList() instead of notifyItemRangeRemoved. Since you're throwing out everything it makes more sense to just use the "everything has changed" notify call. I'm not sure if using a rangeRemoved call followed by a rangeInserted one might cause problems - it shouldn't, but depending on how it's resolved, there could be some quirks.
The other thing to check is that you're definitely only updating your adapter once - if whatever's causing that update fires twice in a short space of time, you could see it glitch

ListAdapter DiffUtils newItem and oldItem the same when submitList() called

Just FYI, I'm not exactly looking for a 'fix' but for an explanation and a discussion that might help understand a little bit more how seemingly silly things like these work.
I was working on this bigger project when I realized that somewhere, a certain list wasn't being updated correctly. Looking a little closer, the items, were correctly being modified, and if you 'scrolled away' and back, the item's information would be displayed correctly.
I stumbled upon this article:
ListAdapter not updating item in RecyclerView
But the difference here, is that in fact, DiffUtils was being called, but somehow the newItem and oldItem were the same! I understand that the library assumes you are using Room or any other ORM which offers a new async list every time it gets updated, but here's the thing. If I submit the list "naively" DiffUtils is not even called. But, if I submit the list as list.toMutableList() like some suggest then, DiffUtils IS called, but somehow the items, new and old, are already the same, hence, nothing gets updated at that moment (verified this by placing breakpoints inside areContentsTheSame).
I leave you here the relevant snippets and a link to a test project I created just so I could encapsulate the behavior and test it separately from everything else.
The Fragment - just calling the submitList
viewModel.items.observe(viewLifecycleOwner) {
adapter.submitList(it.toMutableList())
}
ViewModel
private val _items = MutableLiveData<List<SimpleItem>>()
val items: LiveData<List<SimpleItem>>
get() = _items
init {
_items.value = ItemsRepo.getItems()
}
fun onItemClick(itemId: Int) {
ItemsRepo.addItemCount(itemId)
_items.value = ItemsRepo.getItems()
}
The "Repo" I create some data
object ItemsRepo {
private var items = mutableListOf(
SimpleItem(1),
SimpleItem(2),
SimpleItem(3),
SimpleItem(4),
SimpleItem(5)
)
fun getItems(): List<SimpleItem> {
return items
}
fun addItemCount(itemId: Int) {
items.find { it.itemId == itemId }?.let {
it.itemClickCount += 1
}
}
The GitHub repo:
https://github.com/ellasaro/ListAdapterTest
Cheers!
Don't use mutable data classes or mutable lists with DiffUtil. It can lead to all kinds of problems. DiffUtil relies on comparing two lists, so if one of them is mutable and has been changed, it can't compare old and new successfully because there's no record of the previous state.
I didn't take the time to narrow down your exact issue, but I bet if you change your Repo's getItems() to return items.toList() (so mutating the Repo doesn't mutate downstream lists), and change SimpleItem to be an immutable class, your problems will go away.
Making SimpleItem immutable will be a little bit of hassle, unfortunately. The click listener instead of mutating the item will have to report back to the repo the id of the item that changed, and the repo must manually swap it out, and then you refresh the list.
It will be cleaner if your Repo returns a Flow of lists that automatically emits when changes are reported to it. Then your ViewModel doesn't have to both report changes and then remember to manually query the list state again.
I would use toList() and not toMutableList(). A mutable list communicates that you plan to mutate the list instead of just readding it, which you must never do with a list being passed to a DiffUtil.
Declaring the itemClickCount property as val, and getting the list as an immutable list from the Repo object did the trick as Tenfour04 suggested.
As an additional observation, if I keep the itemClickCount property as var but replace the element altogether and re-submit the updated list, it works correctly. So the problem seems to be modifying the object's mutable property directly in the Repo's list. Using .toList() in getList() didn't help in that case.

Detecting the empty PagedList result in Android

I am using the Paging library from Android Jetpack to have a paged loading behavior in my RecyclerView. Now I want a simple thing - get a signal in my UI that there is no data and the list is empty, so that I can show a message like "there are no items".
The problem is that I'm using PositionalDataSource without placeholders since I have no idea how big the list will be. Another problem is that I can only take the loaded items from the PagedList so I have no idea if more data is being currently loaded from my DataSource.
So the question is - does the PagedList or DataSource give out a signal like "i'm done loading"? That event is clearly defined in the library, since it will stop loading once it gets less data than asked, as mentioned here: Returned data must be of this size, unless at end of the list. The question is - can I get that event signaled to me somehow?
For now I see the following solution. I have implemented my DataSource.Factory just like in the Android Guide shows in this page: giving out my DataSource as a LiveData in factory. Besides, I already exposed a LiveData object from DataSource called isLoading, I use it in the UI to show a progress bar every time DataSource loads something. I'm thinking to add another LiveData called emptyResults and then I can wire both together in my UI so that I will show my "no items" message when emptyResults && !isLoading.
I wonder if there is a better way to do this.
This solution worked for me:
Add an adapter observer:
adapter?.registerAdapterDataObserver(adapterObserver)
Detect if the list is empty and 0 items are inserted
private val adapterObserver = object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
val count = adapter?.itemCount
if (itemCount == 0 && count == 0) {
// List is empty
} else {
// List is not empty
}
}
}

DiffUtil in Xamarin.Android

Junior developer here so please play nice :)
My app uses a RecyclerView to display a list of items returned from a server. The adapter and refreshing works fine however, the app hangs/freezes temporarily when updating/refreshing the list.
I'm confident that it's freezing when it hits NotifyDataSetChanged() since this redraws everything in the list (there can be hundreds of items in the list). After looking online, it appears that DiffUtil may be exactly what I'm after but I can't find any documentation or tutorials for Xamarin.Android, just regular Java based Android and I don't understand either language enough to translate it.
If anybody can point me in the right direction it would be much appreciated!
I was able to get DiffUtil working in Xamarin.Android after reading this article from VideoLAN: https://geoffreymetais.github.io/code/diffutil/. He explains it very well and the examples in his project are very useful.
Below is a "universal" version of my implementation. I would recommend reading up on what each of the override calls does before implementing your own callback (refer to link above). Believe me, it helps!
The callback:
using Android.Support.V7.Util;
using Newtonsoft.Json;
using System.Collections.Generic;
class YourCallback : DiffUtil.Callback
{
private List<YourItem> oldList;
private List<YourItem> newList;
public YourCallback(List<YourItem> oldList, List<YourItem> newList)
{
this.oldList = oldList;
this.newList = newList;
}
public override int OldListSize => oldList.Count;
public override int NewListSize => newList.Count;
public override bool AreItemsTheSame(int oldItemPosition, int newItemPosition)
{
return oldList[oldItemPosition].Id == newList[newItemPosition].Id;
}
public override bool AreContentsTheSame(int oldItemPosition, int newItemPosition)
{
// Using JsonConvert is an easy way to compare the full contents of a data model however, you can check individual components as well
return JsonConvert.SerializeObject(oldList[oldItemPosition]).Equals(JsonConvert.SerializeObject(newList[newItemPosition]));
}
}
Instead of calling NotifyDataSetChanged() do the following:
private List<YourItem> items = new List<YourItem>();
private void AddItems()
{
// Instead of adding new items straight to the main list, create a second list
List<YourItem> newItems = new List<YourItem>();
newItems.AddRange(items);
newItems.Add(newItem);
// Set detectMoves to true for smoother animations
DiffUtil.DiffResult result = DiffUtil.CalculateDiff(new YourCallback(items, newItems), true);
// Overwrite the old data
items.Clear();
items.AddRange(newItems);
// Despatch the updates to your RecyclerAdapter
result.DispatchUpdatesTo(yourRecyclerAdapter);
}
It is possible to optimise it even more by using custom payloads etc. but this is already head and shoulders above calling NotifyDataSetChanged() on your adapter.
Last few things that I spent a while trying to find online:
DiffUtil does work in fragments
DiffUtil can update an empty list (i.e. there doesn't need to be pre-existing data)
The animations are handled by the system (i.e. you don't have to add them yourself)
The method that calls DispatchUpdatesTo(yourRecyclerAdapter) does not have to be within your adapter, it can be within your activity or fragment
This is quite new for me too, and I have seen this before. I tried it literally just now, and got it working after a half an hour.
So some of it comes from here: https://medium.com/#iammert/using-diffutil-in-android-recyclerview-bdca8e4fbb00
And basically what it says is to:
Have 2 different points of your data structure (List, IEnumerable, etc...) it sounds like you already have that, so that's good.
Have a DiffUtil.Callback class where you'll be passing in the old and new data that this class will compare one against the other.
Have a method that will dispatch the updates along with your utility class. Though how the post has it is a bit wrong since he didn't update the old data. But if you did that, then it'll have to work as it does for me.
Let me know if you have questions or run into issues.

Paging Library with custom DataSource not updating row on Room update

I have been implementing the new Paging Library with a RecyclerView with an app built on top of the Architecture Components.
The data to fill the list is obtained from the Room database. In fact, it is fetched from the network, stored on the local database and provided to the list.
In order to provide the necessary data to build the list, I have implemented my own custom PageKeyedDataSource. Everything works as expected except for one little detail. Once the list is displayed, if any change occurs to the data of a list's row element, it is not automatically updated. So, if for example my list is showing a list of items which have a field name, and suddenly, this field is updated in the local Room database for a certain row item, the list does not update the row UI automatically.
This behaviour only happens when using a custom DataSource unlike when the DataSource is obtained automatically from the DAO, by returning a DataSource Factory directly. However, I need to implement a custom DataSource.
I know it could be updated by calling the invalidate() method on the DataSource to rebuild the updated list. However, if the app is showing 2 lists at a time (half screen each for example), and this item appears in both lists, it would be needed to call invalidate() for both lists separately.
I have thought with a solution in which, instead of using an instance of the item's class to fill each ViewHolder, it uses a LiveData wrapped version of it, to make each row observe for changes on its own item and update that row UI when necessary. Nevertheless, I see some downsides on this approach:
A LifeCycleOwner (such as the Fragment containing the RecyclerView for example) must be passed to the PagedListAdapter and then forward it to the ViewHolder in order to observe the LiveData wrapped item.
A new observer will be registered for each list's new row, so I do not know at all if it has an excessive computational and memory cost, considering it would be done for every list in the app, which has a lot of lists in it.
As the LifeCycleOwner observing the LiveData wrapped item would be, for example, the Fragment containing the RecyclerView, instead of the ViewHolder itself, the observer will be notified every time a change on that item occurs, even if the row containing that item is not even visible at that moment because the list has been scrolled, which seems to me like a waste of resources that could increase the computational cost unnecessarily.
I do not know at all if, even considering those downsides, it could seem like a decent approach or, maybe, if any of you know any other cleaner and better way to manage it.
Thank you in advance.
Quite some time since last checked this question, but for anyone interested, here is the cause of my issue + a library I made to observe LiveData properly from a ViewHolder (to avoid having to use the workaround explained in the question).
My specific issue was due to a bad use of Kotlin's Data Classes. When using them, it is important to note that (as explained in the docs), the toString(), equals(), hashCode() and copy() will only take into account all those properties declared in the class' constructor, ignoring those declared in the class' body. A simple example:
data class MyClass1(val prop: Int, val name: String) {}
data class MyClass2(val prop: Int) {
var name: String = ""
}
fun main() {
val a = MyClass1(1, "a")
val b = MyClass1(1, "b")
println(a == b) //False :) -> a.name != b.name
val c = MyClass2(2)
c.name = "c"
val d = MyClass2(2)
d.name = "d"
println(c == d) //True!! :O -> But c.name != d.name
}
This is specially important when implementing the PagedListAdapter's DiffCallback, as if we are in a example's MyClass2 like scenario, no matter how many times we update the name field in our Room database, as the DiffCallback's areContentsTheSame() method is probably always going to return true, making the list never update on that change.
If the reason explained above is not the reason of your issue, or you just want to be able to observe LiveData instances properly from a ViewHolder, I developed a small library which provides a Lifecycle to any ViewHolder, making it able to observe LiveData instances the proper way (instead of having to use the workaround explained in the question).
https://github.com/Sarquella/LifecycleCells

Categories

Resources