DEV Community

Cover image for How to Avoid Blinking in Android Recycler View
Sergey-Sharyk
Sergey-Sharyk

Posted on

How to Avoid Blinking in Android Recycler View

A bit about RecyclerView

RecyclerView is a more flexible and efficient alternative to the classic ListView widget. It provides options for displaying and transforming a large amount of data in a list or grid view. RecyclerView has a modular architecture and allows you to reuse components for different data types, supports animations and smooth list scrolling. Due to its flexibility and efficiency, RecyclerView has become a standard tool for working with lists of data in Android development and is used in most modern applications that contain lists of data.

The main advantages of RecyclerView over the legacy ListView are reusability and lazy item loading: list items are loaded only as needed. For example, when scrolling through a list, new items can be loaded only when they become visible on the screen. When an item goes off-screen, its View and data are linked to a new item that comes within the scope. This reduces the number of View’s created and destroyed, which reduces memory overhead and improves the responsiveness of the application.

However, it’s not without a spoonful of tar in this honey barrel. When data changes, the list item must be rendered again and receive animation changes. However, such a complete refresh can be undesirable when it is sufficient to make changes to only a small part of the interface (an individual list item). As a consequence, the situation shown in the example below can occur: when you click on the header, the entire element flickers, which negatively affects the user experience.

For example, folder item collapse/expand and each progress notification re-triggers alpha animation of the updated item
So how can this be avoided? In this article, let’s look at two possibilities.


Stable IDs

The first option, Stable IDs, is relevant when list items are moved, deleted, or added. StableIds allow RecyclerView to determine exactly which items have been changed, moved, or deleted between the old and new datasets. This allows RecyclerView animators to correctly perform animations of visual changes to items and allows the user to understand what is happening.
In case RecyclerView detects that two items in the new and old dataset have the same ID (this is done by implementing the getItemId method), the same ViewHolder will be used for them, i.e. in this case RecyclerView will refrain from recreating the View and try to make the most of the existing one.
This is how the process of working with stableIds in RecyclerView works:

Image description

  1. Setting the setHasStableIds flag:
    To enable the use of stableIds in RecyclerView, the developer must call the setHasStableIds(true) method for the RecyclerView adapter. This tells the RecyclerView that the list items will have stable identifiers.

  2. Implementation of the getItemId() method:
    The RecyclerView adapter must override this method to return a unique identifier for a list item based on its position. Such a unique identifier could be, for example, a numeric representation of the item or its hash code

  3. Comparing the current identifiers with previous identifiers:
    When RecyclerView updates the list of items, it compares the stable identifiers of the current and previous datasets to determine changes and moves. If an item retains its stable identifier, RecyclerView can realize that the item has not changed and does not need to update its UI.

  4. Optimized update methods:
    RecyclerView provides a set of updated methods for handling list items, such as notifyItemChanged(), notifyItemMoved(), and notifyItemRemoved(). When using stableIds, RecyclerView can effectively apply these methods by only updating items that have actually changed.


DiffUtil

The popularity of RecyclerView was so great that the Google team returned to develop and refine it after a long time using the first stable version. At the same time, it was obvious that many developers did not fully understand the principle of view reuse, and instead of selectively updating only the items that really need it, they simply called notifyDataSetChanged(), which leads to recalculation and redrawing of all the items visible on the screen. Therefore, one of the big improvements has been the active implementation of the DiffUtil mechanism.

What is DiffUtil? This utility provides a mechanism to automatically detect differences between two data lists and calculate the necessary update operations (insert, delete, move list items). To do this, DiffUtil compares data objects based on a unique identifier or hash code. Then, based on the calculated differences, RecyclerView can update only those list items that have changed, minimizing the load on the application and improving performance. To compare old and new data and calculate the difference between them, several methods of the DiffUtil.Callback abstract class must be mandatorily overridden:

  • areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean:
    Checks whether the specified items in the old and new list are the same object. If the method returns true, the items are considered to be the same and further checks are required to determine if changes have been made;

  • areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean:
    checks whether the specified items in the old and new list have completely identical contents. If the method returns true, then nothing has changed for the item and it should not be redrawn or changed in any way.


DiffUtil with payloads

Image description

Using the DiffUtil mechanism by itself only allows you to optimize element updates in their entirety. In order to work at an even more granular level, payloads should be addressed.
Payloads in DiffUtil RecyclerView are used to update specific list items or parts of them without completely redrawing the entire list. This allows visible items to be updated more efficiently, reducing CPU load and interface refresh.
Payloads are arbitrary objects that are passed to DiffUtil methods to calculate the difference between the old and new datasets and apply the updates to the RecyclerView. The order in which they are used is as follows
As you can see from the diagram, the logic common to DiffUtil of comparing old and new data and calculating the difference between them (using the areItemsTheSame and areContentsTheSame methods) is executed first. However, two more steps are performed next:

  • payload calculation: DiffUtil.Callback#getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any?
    this method returns the payload for the specified items if they have different contents.

  • usage of the calculated payload: RecyclerView.Adapter#onBindViewHolder(holder: ViewHolder, payload: MutableList<Any>):
    here you can check for payloads and perform appropriate updates only for the changed parts of the list item, minimizing redraws and improving performance.
    Using payloads allows you to efficiently update only the parts of list items that have changed, instead of completely redrawing the entire list. This is particularly useful when list items contain complex structures or it may be less costly to update only parts of items rather than the entire contents. It also allows you to implement animations and custom effects, focusing on the specific changes that need to be visualized.


Conclusion

As a conclusion, I would like to present a comparison of two versions of the adapter: the first one corresponds to an inefficient primitive solution without change checking, while the second one contains code that takes into account the payload when changing elements

What exactly has changed?

First of all, Stable IDs are connected. For this purpose, a flag is set and a method is implemented that returns a unique ID for each item in the list:

Image description

Secondly, a mechanism of partial updating of items has been implemented. To do this, the new and old elements are first compared and what exactly has changed is calculated:

Image description

For example, here the list of items consists of two types of items — folders (which can be opened and closed) and files inside them (which can be absent on the device, downloaded with progress displayed, and present on the device). Accordingly, for each type, a different set of possible changes between the old and new state is computed. For files, the set of changes (presence on device flag and download progress) is placed in a list, for folders, for simplicity, the only possible changed item (collapsed/uncollapsed folder flag) is returned. If no changes are detected, getChangePayload returns simply null

Next, the calculated changes are processed each time the ViewHolder is accessed:

Image description

To do this, for each of the provided change element, its type and associated values are determined (with type conversion, since initially the method works with the Any type — well, and then the necessary adjustments are made in the user interface (in this case, custom bind methods are called, each of which makes small changes (for example, changing the progress value or icon)

Using the updated adapter avoids unnecessary animations of elements, as you can see in the video below:

No blinking while downloading

Top comments (0)