I'm displaying a variable number of ViewHolders in a RecyclerView, each with a unique CountDownTimer in it, displayed in a TextView.
If I don't have a large number of ViewHolders in the RecyclerView then the CountDownTimers tick down as expected, second by second.
However, my issue arises when I have a large number (> 10 or so) of items in the RecyclerView. When I open the Activity with the RecyclerView, I see the CountDownTimers displayed to me functioning properly, however as I scroll up and down the entire RecyclerView, the CountDownTimers begin to malfunction.
By malfunction I mean that they fluctuate. For instance, one CountDownTimer will display 20:15 then jump to 30:16 then back to 20:14 then back to 30:15. It's very odd.
This is what I believe the issue is: as I scroll down the RecyclerView, ViewHolders with CountDownTimers disappear but their CountDownTimers are not destroyed, and when I scroll back to those ViewHolders there are multiple CountDownTimers being assigned to the TextViews which display their respective countdowns.
My question is this: Assuming what I described is indeed the issue, how do I cancel CountDownTimers in RecyclerView ViewHolders as they're scrolled past?
From My understanding about RecyclerView :
RecyclerView creates viewHolders as many as it needs to display plus one or more extra for smooth scrolling. When it needs to show new item it checks that any unused viewHolder existed. If existed then it reused the existing one otherwise creates new.
In your condition when you scroll very long then it tries to use existing viewHolder which might be started count down. Now when new item binds it starts another count down. Thats why it shows malfunction.
So, You can add a check & stop count down before starting a countdown. Hope it will help you.
Thanks in advance.
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 used the RececlerView with the ViewHolder pattern for a while now.
Im am implementing a custom Adapter.
Im am not searching for a specific bug help in my code.
I was just wondering, if it's normal, that the onBindViewHolder method is called multiple times (for the same item) while scrolling to the end of the list and scrolling back up. In this case onBindViewHolder is called again for item 0 and 1 (the list contains 7 items in total)
Is there any possibility for this method to get called AGAIN without notifying that the datasat has changed?
Im a bit confused.
Kind Regards,
Palm
Yes it is perfectly normal for a RecyclerView to call onBindViewHolder() multiple times.
A RecyclerView only creates minimum number of Views needed to fill the screen. And it works by reusing the old/created Views. So that when you are scrolling down the View that hid during the scrolling to the top is removed and brought next to the last visible View and added there. But since the View is currently bound with old data onBindViewHolder() is called again to ensure that the View is bound with only the correct data before it is rendered.
Similarly you'll notice that onCreateViewHolder() is only called the exact minimum number of Views it needs.
For a better understanding of how the RecyclerView works I suggest you read up on Recycler, LayoutManager and Recycler.Adapter the three core parts of a RecyclerView.
I have a vertical RecyclerView with LinearLayoutManager with three item types and three different ViewHolders. It is a screen with comments, where first row is a post, rest are the comments and last one is an "infinity scrolling" loading row.
The first row with a post has a countdown, which I update every second via notifyItemChanged(0, "countdown"). The post is correctly updated, the bind is called with the correct payload. Below the post a one comment is visible only.
The problem is that this notifyItemChanged(0, "countdown") calls bind also for the invisible rows 2 and 3. If I update the layout of the post so two comments are visible, then only update for the invisible row 3 is called (plus bind with payload on the post). These binds are called without the payload, obviously. So it does full reload of the rows' data, which I do not want, it is a waste.
Now the funny thing, if I scroll the RecyclerView a tiny bit (even 1 px) this behaviour stops and just the first row, the post, is receiving the bind call. And when I scroll back to top the bind is not called for the invisible cells. So after a tiny scroll all this is fixed.
Other screen with posts only + infinity scrolling row does not behave like this and works as it should.
This is happening with RecyclerView from support library 27.0.2.
EDIT:
This is happening with prefetch on and off, and with different values e.g. 2, 4.
Is is possible to have race conditions between the notify* methods of a RecyclerView.Adapter and scrollToPosition (and smoothScrollToPosition) of the RecyclerView itself? If so, how can I force the scroll to happen strictly after the notify has been applied?
In a bit more detail: I have a RecyclerView with an adapter that frequently is updated with new items (which may or may not overlap with the previous items). Also, whenever I set new items I also want to set the scroll position to a specific item. To that end, I first update the items inside my Adapter and then scroll the RecyclerView.
However, more often than not the scroll position will be wrong after this process. Also, if I then issue another smoothScrollToPosition command without changing the data, the scrolling is weird: It sometimes goes in the wrong direction, etc. After this second scrolling, the position is always correct however. So, it seems that something goes wrong the first time and the RecyclerView catches and corrects that error on the second scroll.
Also, the errors are slightly different when I use notifyDataSetChanged from when I use DiffUtil.
Now I've read in this response by Yigit that notify* is basically asynchronous, so I suppose there can be a race condition between them and the subseqent scrollToPosition - is that correct?
Finally what can I do to establish a strict ordering, so that the scroll is only called when all ViewHolder updates triggered by notify are done?