Search code examples
androidandroid-recyclerviewandroid-viewholder

RecyclerView recreating ViewHolder instead of rebinding


Basically I have a RecyclerView with chat messages. The scenario is as follows:

  1. I add a single message -> Causes RecyclerView to add a new ViewHolder
  2. Delay of about 2 seconds
  3. I change the first message and add a new one -> Should cause 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.

Small video of the flickering: flickering effect


Solution

  • 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