DiffUtils vs stable Ids in recycler view - android

I have been reading a lot about the optimisations in RecyclerView
for quite some time and have learnt a lot of new concepts. One thing that isn't still clear is that can we use both stable ids and DiffUtils together in a RecyclerView. Are there possible benefits/drawbacks of this approach? From what I have read, I think using DiffUtils alone will give all the possible benefits of the reuse of viewHolders and nice animations(correct me if I am wrong). A detailed comparison would be really helpful.

Long story short, DiffUtill fully replaces stableIds approach.
StableIds is a legacy approach from the times when everybody was migrating from ListView to RecyclerView. Along with ItemAnimator it provided an easy approach to get simple predictive animations out of the box. Predictive meaning that RV could deduce itself which items are added/removed or moved when you just called notifyDataSetChanges without bothering about other callbacks.
Once DiffUtil came along there were no need to RV to deduce that, cause DiffUtills tells RV exactly which items are being moved/added/removed.
I use RV in very tough conditions with dozens of item types, and multiple data updates per second, and spend dozen hours debugging RV and animations internals and didn't notice any significant changes in its scrapping/de-scrapping/1-2-3-steps-layouting behaviour when was trying to add stableIds on top of DiffUtil.
Here's a big piece on how animations worked in circa 2015 pre DiffUtill:
https://www.birbit.com/recyclerview-animations-part-1-how-animations-work/
https://www.birbit.com/recyclerview-animations-part-2-behind-the-scenes/
And a little bit more if you're interested in RV internals:
https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714

Yes, you can.
If you use androidx.recyclerview.widget.ListAdapter (which you should), you actualy has to provide a DiffUtil. Just look at the constructor. And then nothing is stopping you from providing stable ids.
Actualy, if you don't use stable ids, and submit a new list with event just a minor change, all the items in RecyclerView will "flash". But if you use stable ids, the change will animate nicely.
Also know, that they have no dependency on each other, you'll just probably use the same id (or whatever) field from your items in both getItemId() and DiffUtil.

As per docs DiffUtils:
DiffUtil is a utility class that can calculate the difference between
two lists and output a list of update operations that converts the
first list into the second one.
It can be used to calculate updates for a RecyclerView Adapter. See
ListAdapter and AsyncListDiffer which can compute diffs using DiffUtil
on a background thread.
DiffUtil uses Eugene W. Myers's difference algorithm to calculate the
minimal number of updates to convert one list into another. Myers's
algorithm does not handle items that are moved so DiffUtil runs a
second pass on the result to detect items that were moved.
and as per this Answer StableIds is
Stable IDs allow the ListView to optimize for the case when items remain the same between notifyDataSetChanged calls. The IDs it refers to are the ones returned from getItemId.
Without it, the ListView has to recreate all Views since it can't know if the item IDs are the same between data changes (e.g. if the ID is just the index in the data, it has to recreate everything). With it, it can refrain from recreating Views that kept their item IDs.
Hope you concept will clear now.

Related

Android Recyclerview StableIDs when Replacing Entire List

According to Android documentation:
Enabling Stable IDs will make the recyclerview do less work when
updating the list. Even if we call the notifyDataSetChanged(), it does
not have to handle the complete re-ordering of the whole adapter
because it can find if the item at a position is the same as before,
which results in faster performance of the recyclerview.
It also states that I should prefer notifyOnItemChanged(), notifyOnItemRemoved(), notifyOnItemInseted() etc. instead of notifyDataSetChanged() and prompts me to use it as last resort.
However, what happens if I simply want a recyclerview that doesn't support operations such as add/remove/replace at position and It is designed to replace the entire list at once? For example, I might want to display either Popular or Top Rated movies, which means that the entire content in the recyclerview will be changed. In this case, is it faster to simply:
setHasStableIDs(False);
notifyDataSetChanged();

Which is more efficient DiffUtil or SortedList?

I recently came across the concept of using Diffutil and SortedList.Callback<> to change data in instead of notifyDataSetChanged() directly but now I am confused which one to use at which condition.
According to the documentation for sortedList:
A Sorted list implementation that can keep items in order and also notify for changes in the list such that it can be bound to a RecyclerView.Adapter.
It keeps items ordered using the SortedList.Callback.compare(Object, Object) method and uses binary search to retrieve items. If the sorting criteria of your items may change, make sure you call appropriate methods while editing them to avoid data inconsistencies. You can control the order of items and change notifications via the SortedList.Callback parameter.
So, sortedList is a different implementation of List with more functionalities and capabilities. For example:
Keeps last added items. When you update a common List with values that already exist in it tou will end up with duplicate values, while sortedList replaces items that are the same using the Callback's areItemsTheSame.
Updates views smartly. When new items are added, onChange will only be called if one or more elements of the content changed.
Better performance, If you are going to add multiple items to a SortedList, BatchedCallback call converts individual onInserted(index, 1) calls into one onInserted(index, N) if items are added into consecutive indices. This change can help RecyclerView resolve changes much more easily.
On the other hand, according to the documentation for diffUtil:
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.
It can be used to calculate updates for a RecyclerView Adapter.
So, diffUtil is a great tool that gives you the ability to compare two lists and find their differences. You can then use these comparison results to do whatever you want, but it doesn't do much more. On the above link you can find more about what's going on under the hood and its performance.
Conclusion:
sortedList is a List with more functionalities, giving you more capabilities than a common List. But, also has different behavior in some cases than a common List.
diffUtil is a great comparison tool for list contents. But, depending on the case it may not provide the best performance. Also, it needs to be used in background thread and the content of the lists it's comparing shouldn't change during the comparison process.
In both, read the documentation before using so as not to have unexpected results.

Nested RecyclerView creates all ViewHolders at once

I have a rather complicated List with nested RecyclerViews. I get it that nested RecyclerViews aren't the best solution, but in my case it is one of few solutions that create structured code and meet the requirements. I have attached an image of the structure. You can take telegram as an example to improve your understanding of the structure. Basically I have an outer RecyclerView RV-1 with Items RV-1-Item and an inner RecyclerView RV-2 with Items RV-2-Item. So far so good, my problem is that the outer RecyclerView recycles views as intended, but if one of the RV-1-Items comes into view, all ViewHolders of RV-2 are created (That means that sometimes more than 100 ViewHolders are created). To sum it all up my question is how to force the inner RecyclerView RV-2 to recycle ViewHolders as well.
I know that the inner RecyclerView RV-2 has to have a hight of wrap_content because it depends in the count of the inner items, also i cannot set setHasFixedHeigth(true) (and I don't know if it would help) because during runtime new RV-2-Items can be added into RV-2. I also tried to set setNestedScrollingEnabled(false) on RV-2 because I read a lot about it online but it didn't help me either.
So basically this is how I configure
RV-1
layoutManager = LinearLayoutManager(context)
isNestedScrollingEnabled = false
RV-2
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context).apply {
reverseLayout = true
}
In addition to that I have some ItemDecorators but they only create the space between the items, so they shouldn't have to do anything with the problem.
To sum it all up:
The outer RV-1 recycles it ViewHolders as intended but the inner RV-2 creates all ViewHolders at once, even if they are not on screen. I assume that this is the case because RV-2 has a height of wrap_content and when the layout_height need to be measured it needs to create all views. THE QUESTION: Is there a way to force RV-2 to recycle its views?
EDIT:
Also I am using a shared RecycledViewPool between all RV-2 RecyclerViews but that isn't really related to the problem, because even if the ViewHolders are shared between the RecyclerViews, an RV-2 RecyclerView shouldn't create ViewHolders that aren't visible when it is initialised.
EDIT 2:
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android, in case all visitors of this question think the same my question is: How would you implement such a structure. It is obvious that I could make a single view which has a IM (Round Image View) and RV-2-Item and just make the IM invisible when it isn't needed. In my opinion this somehow makes the structure more complicated. Furthermore a requirement is that the IM on the left side of RV-1-Item must have the ability to move up and down in RV-1-Item, which is obviously easier with my current structure.
EDIT 3: (My last one I promise)
The Problem I have shown can be solved by using the approche I explain in my EDIT 2, even if it isn't the best solution it would work. But the issue is that I have an even more complex screen where this approche wouldn't work anymore because I have three nested RecyclerViews. I could get that number down to two with the approche of EDIT 2 but I would still be left with two nested RecyclerViews and I cannot think of a workaround that could solve the problem of the remaining two nested RecyclerViews. I attached an image of the even more complex screen which contains a the interface of the app with marked sections to help you to understand the structure.
(Not quite an answer to your specific question in solving "how to not get the RecyclerView to create all items at once", but something that most likely will fix your specific problem by not using nested recyclerviews at all)
I would suggest (in a quite similar way as already suggested in this answer), to flatten your feed into one recyclerview
(No matter how much you tweak your nested recyclerview architecture, imho it will never be as performant than having just one recyclerview, and as you don't need nested scrolling (I guess), just one recycler view should be your best option).
I would propose to not think of your feed in the way your data is structured, but in a way you want to show it and how it can be split into smaller items which are "look alikes" / consist of the same things.
From your screenshot I would see for example the following items / view types for each chat item:
Chat header (the thing with the icon and the text "New Group")
the user badge (the picture with the text "Jürgen")
a message item (one bubble of text, so e.g. in your screenshot at the bottom there would actually be 3 of those items, one for each message)
The section with the date and the action/reply items.
Those items are way smaller than a whole chat item, and therefore can be faster created / recycled.
For each of those items, create a view-type and a view-holder, and treat them as seperate recycler-view items.
The recyclerview will, when the getItemViewType method is correctly used, create / prepare the correct type of view for the position you need.
For this to work, the adapter needs to add some logic, as your data most likely will be structured something like
a list of chats, and each chat has a name and some messages to display
and we need it as
the first 6 elements are for the first chat, where the first position
is the header, the second the user badge, the next 3 items are message
items and then we need an action item.
So you basically need to calculate how many recyclerview items you will need to show each single chat-item, which could be a calculation along the lines:
1 chat header item + 1 user badge item + 3 message items + 1 action/reply item = 6
This calculation needs to be performed for each chat item of your data list separately.
So if you only have this single chat item in your list of data to display, you actually need to tell the adapter to create 6 items (by returning in this case 6 at getItemViewCount()).
Then, you need to tell the adapter using the getItemViewType(position: Int) function, at which position of the recyclerview which type of view the adapter needs to prepare.
So there you again need some logic to say that e.g. on position 0 the chat header for the first chat item should be, at position 1 the user badge for the first chat item, at position 2-4 message items should be, on position 5 the action item and at position 6 the chat header for the second chat should be and so on
(again, the logic then needs to be in place for all chat items, and it can get really messy / complicated, as to calculate each chat items view types for a position, e.g. all prior chat element view counts need to be recalculated, too (in order to know at which recycler-view position your current chat item starts)).
As this tends to blow your adapter up, I would suggest (if you don't already do so), to get some manager / delegate architecture in there.
So e.g. have a delegate for each view type, and a manager which calculates the number of recyclerview items / view types needed for each chat item.
Just for reference:
Some time ago we had a situation similar to yours
(a recycler-view with a design similar to a social media feed, which should show the first n comments in the feed and we displayed the comments for each feed item (which was a recyclerview item) with another recyclerview in the item) and also after some troubles with performance which we could not manage to resolve just flattened the recyclerview, and never had performance troubles again.
A lot of comments and related questions say that two vertical nested RecyclerViews isn't a possible thing in android
This is not true; whoever says this is not a thing has not done it and thinks it's not possible. It is possible, albeit with complications, side-effects, and most likely, the annoyance of your users when they tap around trying to scroll up/down and the wrong touch interceptor wins.
Why is this a problem?
On iOS, when you try to do something that the platform devs didn't think it was good, most people and other devs scream at you: don't fight the framework!!!.
On Android, we see the craziest Java (and now Kotlin) implementations of things that makes you wonder what are we -developers- learning at school and what are we teaching?! and yet nobody says anything (for the most part) :p
The truth is, you're trying to design a complicated user interaction and data transformation, and yet, your attempt is biased by trying to use the data "as you have it" (which implies dealing with these two different RV/Adapters), as opposed to do what one should do: transform the data for presentation.
This leads me to the next question:
How would you implement such a structure.
Well, for starters, I don't know how your data looks like, nor where it's coming from; I don't know what your users can do with your data, outside of the obvious scrolling.
I also don't know how your data wants to be presented, aside from your mock up.
But I do know the situation very well. A list of things, which also contain their own list of things.
Case: The List of List
It is doable; you can have a list and inside said list, have another list. I've done it. I've seen it done by others. I've used it. I also never liked the idea of having this "small" scrollable thing, fighting to see who scrolls first when I tap "the wrong place".
I would not do this. If the inner-list is big (say more than 3 items per outer item), I would not present it as scrollable content.
What I would do (considering the things I do not know about your problem) is to have a single list displaying all the content properly flattened.
This has a issue with your content:
What if the inner-lists are super long, wouldn't this cause them all to be displayed? YES, and that's why I wouldn't do it this way if the data (as you described) can have 100 items. An options is to display the 3 first items with a "more" link to now open the inner-list "full screen"; this is 10 times better than the nested list from a user's PoV and from the technical aspect of it.
Another alternative, is to keep this single long list (RV-1) and let users "expand" the list to launch another full-screen list depicting the contents of RV-2, in a separate window. This is even better.
The time you'll spend implementing this and getting rid of the mess of code you probably have right now, will make you wonder why didn't you suggest this in the first place.
If this is something you absolutely cannot do, then I cannot offer you much more advice, for now you're tied to unknown to me business/product rules. Ultimately, the price will be paid by the users of your app, when they have to scroll that nightmare :)
Take a Step Back
Let me be clear, I am not criticizing you or your solution; I'm merely pointing out that, in my experience, this "pattern" you have here is not a good user experience.
Format your data for presentation, not the other way around. Your data should be properly shaped so it can be properly presented with the tools you have.
You're fighting against the tools Android is giving you; you're giving a RecyclerView (and its adapter) a lot of new problems to deal with when it already has a lot going on.
Think about it: RecyclerViews have to do a lot of things; Adapters must also conform to a few interfaces, ensure things are dispatched as soon as possible, calculate Diffs (if using a ListAdapter<T,V>), etc. Activities/Fragments? They have a lot on their plates dealing with ... well "Android"; now you're asking all these components to also handle a complicated scenario of scrolling content, touch recognition, event handling, view inflation, etc.
All this, while expecting each view to take 16ms or less (to stay above 60 FPS scrolling speed, your view/viewHolder should not take more than 16ms to do all it needs.
Instead, I'm asking you to take a step back, grab the data you have, compose it, transform it, map it, and create the data structure that can better serve the components you have (a RV + Adapter + a simple View).
Good luck :)

Can the performance of PagedList and RecyclerView interaction be improved?

In my Android app i'm showing data in a RecyclerView using Room and the Paging library.
My implementation is quite similar to the example in the AsyncPagedListDiffer docs.
The flow is the following:
Data is changed in the database
A corresponding Observer passes the changes to the Adapter:
myLiveData.observe(viewLifecycleOwner, Observer {
myAdapter.submitList(it)
})
The AsyncPagedListDiffer calculates the diff and updates the list accordingly
My problem is the performance hit of step 3.
Even if i insert just one single item to the top of the list, the differ have to check all of the items (which is quite inefficient, especially with larger datasets), whereas a simple notifyItemInserted(0) call would be sufficient.
Is there a way around this behavior?
For example, can i tell the differ that it doesn't have to check all of the items? Or any other solutions to this?
I'd really appreciate any advice.

Does using DiffUtil when all items change completely make sense?

I'm making an app which uses RecyclerView to display lists of popular apps or songs (never combined). There are two actions: first updates list just a little bit (adds or removes few items), so using DiffUtil seems a good idea here. But in the second case, all items are changed. Sometimes it's only structural change (when I switch between different apps lists) and sometimes also item types are different (when I switch from apps to songs). I'm new to Android and I'm not sure weather I should use DiffUtil in these cases as well or it only causes unnecessary comparison between lists that are different anyway. And if so, should I use method like notifyDataSetChanged() or just create and set new Adapter with new data?
I've read documentation and some articles on the subject, but I'm still confused, so I'll be very grateful for advice on how to best handle this issue.

Categories

Resources