Improve UI Performance in RecyclerView Layout Part-1

Abdennasser ABIDI
6 min readJan 5, 2023

--

Here is the list of the blogs in this series:

RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very complex. As we all know every UI component in Android if not used properly can lead to poor performance which will hamper the user experience.

if the app is performing better in terms of UI and there is no lag during user interaction then you can be assured for better user experience.

The loading of large XML layouts is one of the major performance bottlenecks in android. Loading XML layout into memory through IO operations and parsing views through reflection can be expensive. This is especially true when the XML file is too large or when initializing the layout view takes a while. We know that when the main thread performs some time-consuming operations, it may cause the page to freeze, and even more, serious ANR may occur.

There are guidelines to prevent performance problems

So let’s go to solve the problem

  1. RecyclerView initializing

If it is possible to set the width and height of the items at the XML file and they don’t change based on adapter’s content(wrap_content), add this line to your RecyclerView Initializing method:

recyclerView.setHasFixedSize(true)

By this method, you told RecyclerView to don’t calculate items size every time they added and removed from RecyclerView and we will gaine some time of calculation.

2. Caching Items

recyclerView.setItemViewCacheSize(20)

Set the number of offscreen views to retain before adding them to the potentially shared recycled view pool.

The offscreen view cache stays aware of changes in the attached adapter, allowing a LayoutManager to reuse those views unmodified without needing to return to the adapter to rebind them.

In other words, when you scroll the RecyclerView such that there’s a view that is just barely completely off-screen, the RecyclerView will keep it around so that you can scroll it back into view without having to re-execute onBindViewHolder()

3. Use setHasStableIds

If your data source contains a list of model and each item has a unique id. You should to call setHasStableIds(true). It will reduce the times your recycler view try to calculate and detect which items are after the data source changed.

adapter.setHasStableIds(true)

After set adapter has stable ids. You have to implement getItemId() and make sure it will return a unique id for each line of your recycler view.

override fun getItemId(position: Int): Long {
val item = getItem(position)
return item.getStableId()
}

4. Recyclerview pool :

In performance we have some issue with nested recyclerview :

This is the most common design for most of the apps. We will have the smooth scrolling for inner horizontal RecyclerView. But when it comes to vertical scrolling then we will see some erratic behavior. This is due to the inflation of every view of nested horizontal RecyclerView.

Solution :

Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a RecyclerView.

The reason behind this behavior is having a separate view pool for every nested view. Setting a Single view pool for all the nested RecyclerView Pools will make the vertical scrolling smooth.

val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
holder.innerRecyclerView.setRecycledViewPool(viewPool)
}

So all the horizontal inner RecyclerView will use the same view pool and can reuse the scrapped views. This will decrease the view creation time and make the scrolling experience smoother for the user.

If 2 recyclerviews can share views(not independent from each other), it will lead to better performance. This will improve the scrolling performance as it will start reusing the view from the shared view pool.

5. Remove layout redundancy :

use Merge tag :

The <merge> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another.

Problem :

if we have multiples nested layouts like this :

Nested Layout

The nested layout serves no real purpose other than to slow down your UI performance.

Solution :

The <merge /> tag is a tag used to help eliminate redundant view groups in your view hierarchy when including one layout within another. For example, if your main layout is a vertical LinearLayout in which two consecutive views can be re-used in multiple layouts, then the re-usable layout in which you place the two views requires its own root view. However, using another LinearLayout as the root for the re-usable layout would result in a vertical LinearLayout inside a vertical LinearLayout. The nested LinearLayout serves no real purpose other than to slow down your UI performance.

Applying merge tag :

including merged layout

Go to the app > res > layout > right-click > New > Layout Resource File and name the file as custom_layout. Below is the code for the custom_layout.xml and the file should be present with merge contents.

add merge file

Doing something as simple as this, reduced the view load and display time by more than 50%, it also eliminated the keyboard ugly blank space issue, because the layout could resize faster.

6. Load views on demand :

ViewStub

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime.

ViewStub a dumb and lightweight view. It has no dimension, it does not draw anything and does not participate in the layout in any way. This means a ViewStub is very cheap to inflate and very cheap to keep in a view hierarchy.

you can reduce memory usage and speed up rendering by loading the views only when they’re needed. This can defer loading resources when you have complex views that your app needs in the future

A ViewStub can be best described as a lazy include.

7. DiffUtils vs notifyDataSetChanged

Problem :

Situations may arise when our recycler view will want to notify registered observers that the data set has changed. This could mean refreshing a page or adding new elements. To tell the Recycler View that an item in the list has changed, we call notifyDataSetChanged(). However, this has a few drawbacks

  • It redraws every view again :

The Recycler view has no idea of changes on the list, thus it assumes that all items are invalid and recreates the views again. There is no way for the RecyclerView to know what the actual changes in the data set are. Thus, it is assumed that all current items and data are invalid. That is why all visible views will be recreated again. This sounds like an expensive operation.

  • Very expensive :

Since it requires new instances and resources each time, it’s a very expensive method(New instance of your adapter). It requires even more resources and time than the previous one. It is easier for the developer just to assign a new instance adapter to the RecyclerView. Although, the price payed in performance is huge.

Solution : DiffUtils

DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. It’s also a helper for Recycler view adapters that calculate changes in lists and minimize modifications.

Benefits :

This class has benefits over notifyDataSetChanged :

  • Only redraws changed items
  • Animates by default
  • Much More Efficient

However, it has a limit , it execute in main thread. But we found a solution for that

AsyncListDiff or ListAdapter (execute in background thread)

I hope you enjoyed this blog and learned something! on the next one, we will be talking about the impact of constraintlayout and databinding on performance

--

--