I have a standard recyclerview that is split up into sections.
For simplicity, let's call them:
Section A
Section B
Section C
When the recyclerview is loaded, I make network requests to fetch data. These network requests are triggered by the user reaching the first item in each section. Meaning, that there aren't three network requests triggered when the recyclerview is loaded, but rather only one.
Once I receive a response with data, I update the recyclerview using the notifyItemRangeChanged method.
When the user scrolls normally, this works flawlessly and I don't see any visible UI issues.
When the user scrolls really fast, from top to bottom, passing all the sections, the UI sometimes freezes all together. When looking at the code and narrowing down the problematic area, I found out that notifyItemRangeChanged is being called three times (as expected) in a very short time interval.
The calls themselves are at most 600 ms apart.
(When I remove the calls to notifyItemRangeChanged, the UI doesn't get stuck)
I understand that calling notifyItemRangeChanged has a costly impact on the UI due to it's inner logic and that is also why I am not calling notifyDataSetChanged.
But since I have not found a viable solution that listens in on when the recyclerview finishes updating the UI, I am wondering what I should be doing.
Nothing relevant or remotely close has come up in my searches.
How can I sync multiple calls to notifyItemRangeChanged? Or should I be using something else?
Related
I have a RecyclerView (1.2.1), with a List adapter, and ViewHolder, backed by a Room PagingSource. There's about 700 items on the list. The paging seems to work fine, and I've flattened my View hierarchy as much as I can.
Upon initially loading the recycler view, everything seems fine. Paging works, everything seems snappy. onCreateViewHolder in my adapter is called 14 times, and initially 5 are visible on the screen.
Slower scrolling is fine (it does call onCreateViewHolder more often than I expected, but there's no jank).
The problem comes when rapidly flinging through the list. After 3-5 fast flings, it appears to decide that it needs to have more cached view holders, and makes many, many calls to onCreateViewHolder - this method is clocking in at ~5ms, but there's just too many of them, and the scrolling stops. It appears to call onCreateViewHolder ~700 times - the same as the number of items on the list, like it's not recycling the views at all.
At that point, sometimes the app will recover, and at that point everything is smooth and it doesn't appear to need to create more ViewHolders. Sometimes however I will get the ANR dialog.
I've tried tweaking the recyclerView.recycledViewPool.setMaxRecycledViews(), but this doesn't appear to to increase the recycledViewCount until after the mass onCreateViewHolder calls.
Is there anything I can do to resolve this? Make the fling speed slower? Tune the view holder recycling somehow so that it doesn't go nuts and try and create so many at once?
I don't think I can get the layout inflation any better, given my design and data constraints. And even if I could, it's still creating waaay too many to be able to get them done in under 16ms!
In my situation, I needed to do 2 things. First and foremost, I needed to adjust my paging configuration. My prefetchDistance was too small in relation to my page size! This got rid of the ANR - no more mass creation of ViewHolders!
The recyclerView would still pause scrolling when loading a page (especially towards the end). I added a loadStateFlow collector that handles showing a loading indicator so that the user knows there's more data coming.
I have a basic RecyclerView setup on a chat-like app and I have hit an issue with the item animations.
The project is making use of Room with Paging 3 and DiffUtils for the RecyclerView adapter, so this is all automated, but the core of the problem can be simplified to this:
When I send a new message, that message is added to the RecyclerView
here the adapter is triggering notifyItemInserted or notifyItemRangeInserted which causes the entire message list to shift up softly and the new message fades in after
I scroll the list to the bottom so the new added item becomes visible
When I receive a read status from the server I update the status of that message
here the adapter is triggering notifyItemChanged or notifyItemRangeChanged which has no default animations on its own, it just updates the item with the new information
All of this is working well on its own, but the problem is when I receive a status update from the server faster than the insert animation has a chance to finish. When that happens the notifyItemChanged or notifyItemRangeChanged kicks in and skips the animation initiated by notifyItemInserted or notifyItemRangeInserted. The list till shifts upwards, but the fade in no longer happens, instead the item is instantly made visible all the while the list is still shifting up, overlaying the item previously occupying that last position causing an ugly visual experience.
I can kinda "cheat" by delaying the step in 2. to engage after the animation is supposedly over, but then it introduces another visual issue if the user sends multiple messages quickly or receives them in the same fashion or in certain cases it just does not show any animation because the new item is loaded outside of the list and only scrolled after the animation time is elapsed, so this is not a solution.
first
second
In this example there are 2 recyclerviews set up with the same adapter slightly changed to make it easier to compare the issue in the same action.
The left recyclerview is not doing any update when an item is inserted, but it is the behavior I expect to display even if I update the item during the item insertion animation.
On the right recyclerview is the actual problem, as you can see new items are showing in full over the old ones before they have a chance to move out of the way.
The first example recording has scroll to bottom with no delay after the item is inserted, the second example has a delay that matches the insertion animation duration.
Reminder: this is just a manual example, the real application in my case is being done automatically via the integration I mentioned above, I am not the one in control of when the notifyItem* calls are made at any point.
How can I make sure the insert animation does not get interrupted even if I am updating the item data in the middle of the animation?
EDIT: I already searched for a solution in the questions posted before, but none are related to this one nor do the similar ones provide a solution to my problem.
I have a ListView with an ArrayAdapter to back it. Updates to the list typically involve multiple item add/remove operations, followed by a sort operation. Often, the update just removes one item and adds an almost identical one, which eventually ends up in the same place as the item I removed.
How can I prevent the list view from “twitching” during these operations? Ideally, I would like to freeze all redraw operations, then add, remove and sort items, and only then “unfreeze” the UI, allowing it to be redrawn. Any way to achieve that?
ArrayAdapter#setNotifyOnChange() seems like a candidate for this. Use as follows:
myAdapter.setNotifyOnChange(false);
// add, remove and sort items here
myAdapter.notifyDataSetChanged();
Ensure nothing else in the update code calls ArrayAdapter#notifyDataSetChanged() (as doing so will trigger a redraw), and updates will be delayed until the transaction is complete.
EDIT: after trying this, there is still the occasional twitch, but that may also be caused by the fact that I scroll the selected list view item (back) into view after each update.
In the beginning we had listViews. As a developer, we had to recycle and reuse the views to have a fluid experience.
Then, came the recylclerviews. Now, all the recycling heavy lifting is managed by android library itself.
Using pagination, an infinite recycler view can be implemented, which loads the data when needed.
But there is one problem I am still facing in infinite recyclerview. How is the data in the adapter managed?
In most of the infinite scroll implementations of recyclerview, the new data is appended to the original data. This makes the size of data set ever increasing.
Why cant dataset itself behave like recyclerview and recycle its data, instead of appending? (Like a circular queue).
How can one manage the positions of itemviews, when the dataset is a circular queue. Is it unnecessary and yields too little performance improvement? Am I missing some design pattern?
It would be possible to clear data already loaded, but this way you have to load data not only for "bottom" views, but for "upper ones" too. So user wants to back to previous data and he needs to load that data again, that's the problem.
There could be more work about notifying data set changed in recyclerView.
You can implement it this way, but remember about pagination and how it is implemented - for example limiting data in SQL statements standard way can output different data each call, so user backing to previous data can see other results! Check Twitter pagination for one of the way to achieve same results each time.
Pros:
less data in memory
Cons:
more work to write the code
more data loading for users (more internet usage, more loading times - when users wants back to previous data)
pagination has to be designed more carefully
I have a problem where calling notifyDataSetChanged() a bunch of times will freeze interaction with a listview for a brief second.
Basically my app loads a bunch of images into a listview. After each is loaded it posts to the ui thread. The ui thread adds the image to a listarray and then calls notifyDataSetChanged().
When you select an image in the list it highlights. When I call notifyDataSetChanged() per image, every one of three taps on the list might actually select. If I throttle down notifyDataSetChanged(), I get a much better rate of tapping and having it select the item.
Anyone have hints on this?
It seems like the 6 thumbs visible (which are unchanged) get thrown out really quick and swapped in again, but the UI shows no indication of them being deleted. During which tapping is an invalid data set.
Don't call notifyDataSetChange() in the UI Thread. it will definitely block the User interaction if it is doing the data call and update the ListView. so do it in the AsyncTask do in background thread which is not doing on the UI thread so it will update the listview when user scroll it