RecyclerView notifyItemChanged calls bind also on invisible cells - android

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.

Related

RecyclerView item inserted with item updated is skipping inserted animation

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.

RecyclerView onBindViewHolder called multiple times for one item

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.

Error with many CountDownTimers in RecyclerView ViewHolders

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.

Is there a listener for ScrollView's scrolling?

I have a TableLayout inside a ScrollView, because of some special needs, I need to do some things when the ScrollView has reached the top. Is there such a listener to listen in on user's scrolling operations? Or, if there is no a ready listener, can I subclass the ScrollView (which will be painful I supposed) to achieve that? What methods do I need to implment? So far, I don't see a solution to this, but I hope it's only because I missed something .....
Thank you!
(In case you're wondering why I want to do this, here is some more background:
I have a flat-file database, I use a TableLayout to show the query results. When the query generates thousands of rows, the time spent on populating the table (using TableLayout.addView() to add rows) becomes unacceptable -- one minute on the emulator! And, there is a special need that when the table shows up, it has to be put at the LAST PAGE. That is, I have to populate the last few rows first, then grow upward, while the display remains unchanged when the table is growing. I tried two approaches so far, none was satisfactory. So I had to downgrade the requirement a little --- I don't populate all 3,000 rows at once, instead, I populate only the last couple of pages, like the last 40 rows or so. This way, the user will see the results displayed immediately. Then, if and when he decides to scroll up, when the program sees it, then I populate a few more pages, etc. Therefore, I need a listener or some way to detect when the table or the scroll view has reached the top. Thank you for reading up to this far!)
You might want to look into extending a BaseAdapter http://developer.android.com/reference/android/widget/ArrayAdapter.html and override getView(int position, View convertView, ViewGroup parent).
In that method you can check the position. So if you start out by populating your list with 10 items and the position is 10 then you know you've reached the bottom of your list and can add more items to your list. You know your at the top of your list if the position is 0. There's also a method in ListView to have your items start at the bottom ( like the messenger app ) http://developer.android.com/reference/android/widget/AbsListView.html#setStackFromBottom(boolean).

ListView's contents scrambled on scroll

So I am having a problem with the different pieces of that make up my ListView. I put them into an ArrayList and use a custom ArrayAdapter to hook up to the ListView, which I have done before so I don't believe there is a problem there. Initially the list seems to have the pieces in the correct order, but then I will scroll down the list and the contents will then load in incorrect order. I then scroll back up and everything is jumbled. Has anyone run into this before?
Thanks
-Jake
Yes your problem is related to the fact that List reuses the views for each row. So say your list can see 5 items, but your ListAdapter has 15 things in it. Android will create 5 + 1 instances of your row view instead of 15. One for each row in the list + 1 for when half of the top and bottom can be seen. When a row is moved out of the visible area the List will recycle that view instance for another row instead of creating a new one. If you don't properly reset all of the user interface components every time you'll get artifacts from other rows showing up. You must make sure that every time you bind your data from the objects in your array list to the view you set every field every time.
For a better description of this see
http://www.youtube.com/watch?v=N6YdwzAvwOA&feature=related

Categories

Resources