Basically I have a RecyclerView
with chat messages. The scenario is as follows:
RecyclerView
to add a new ViewHolder
RecyclerView
to rebind the first message and create a new ViewHolder
for the second one.My problem lies in step 3. as the first ViewHolder
is removed and a new one is created and then bound. This produces a very slight flickering which is a little bit annoying and all in all this shouldn't happen.
I am using DiffUtil
to dispatch changes, the whole setup is a little bit more involved so it is hard to describe the whole picture but I narrowed it down to this:
The DiffUtil
produces the correct result and calls onItemRangeChanged
for the first item and onItemRangeInserted
for the second item. For whatever reason the first ViewHolder is then removed and a new on is created, added and then bound.
Did somebody experienced similar behaviour or has a clue why this could be the case? I tried to debug through the RecyclerView
code but it is a nightmare and I still don't understand the whole thing. What could be the reason that the first ViewHolder
cannot be rebound and a new one has to be created?
If I can provide any more information just ask for it and I will try my best.
A small repository showing the problem can be found here: https://github.com/MaxGierlachowski/recyclerview_viewholder_bug
If you look into the error logs and look at the 4th "MESSAGE" log you see that a onChanged and a onInserted are dispatched to the adapter but there is a new ViewHolder created for the onChanged one. I know that RecyclerView checks for a lot of things like animation completion and so on but I really would like to know why the ViewHolder isn't rebound but recreated.
EDIT:
Something I figured out was that if that createViewHolder
is called by the RecyclerView
the ViewHolder that should be reused is inside the mChangedScrap
list and mState.isPreLayout()
is false so the function tryGetViewHolderForPositionByDeadline()
doesn't search the mChangedScrap
list and creates a new one. Still this shouldn't happen, the ViewHolder
isn't even animated or something and it seems like all animations are finished at that point.
Hey so I figured it out myself but I still don't fully understand why this is necessary. So the ItemAnimator
inside RecyclerView
decides whether a changed ViewHolder will be just rebound or recreated. It will decide it on three factors that are mentioned in the article below. So basically because I was using the DiffUtil
I just had to provide some arbitrary to the onItemRangeChanged
call and it would reuse the ViewHolder
instead of creating a new one.
It was this article that help me understand the problem: https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91