Optimizing RecyclerView : Pre-inflated ViewPool

by | Aug 23, 2022 | Tech

In any real-world android application, Recylerviews generally do not display static data, data is fetched from the server and then displayed on the UI. When we get a response from the server, only then data is given to the Recyclerview’s adapter which then first creates the items if they are not already present in the RecyclerviewPool (initially pool is empty) and then draws them on the screen. Since all this is done just before they need to be displayed, if the layout is complex enough, the UI thread gets really busy which then leads to frozen frames.

Before understanding how this optimization works and how to implement it in your adapters. Let’s see what improvements we are able to achieve using it.

The above screenshot is from Firebase’s Performance Monitoring, showing the decrease in load time after implementing this in our base adapters.

The above graph is from our in-house monitoring framework showing load time in two different versions of the live application. 0.07 and 0.19 are load times in seconds.
From these metrics, we can see that this optimization not only works but can significantly improve performance.

Before digging deep into this optimization let’s briefly discuss how and when views are created.

Unlike onBindViewHolder which is called multiple times, onCreateViewHolder is only called when RecyclerView needs a new ViewHolder of the given type to represent an item. Because the creation of views is very expensive for the CPU, it is done only when it’s needed. That’s why the concept of recycling was introduced to minimize the creation of views by updating the already existing views using onBindViewHolder method.

So, We can significantly improve the performance by making these expensive calls when the UI thread is not doing much.

What are we trying to solve here?

Problem #1:
Recylcerview creates items just before they are to be used and displayed on the UI. This can result in frozen frames if we have complex UI.

Problem #2:
If we have a nested Recyclerview then the child views can only be created after their parents are created because at the time of inflation we need to pass the parent.


The whole idea behind this optimization was that we have plenty of free time on the UI thread between hitting the API and getting the response. We can utilize that time to inflate some views and make a pool of them that can be used later. And thus utilizing the free UI thread and reducing the overall first draw time for the Recyclerview, leads to a reduction of frozen frames.

Alright, I know all this sounds like magic but it’s time to reveal the trick.

For this, we will make a separate class PrefetchViewPool and make an object of it inside our custom adapter.



I know the name sounds cool 🙂 but what it does is even cooler 🤩.

Firstly, We need our custom adapter to tell PrefetchViewPool about the count and the layout ID of the viewType we want to pre-inflate.

For this, we will maintain three HashMaps in PrefetchViewPool

  1. prefetchViewTypeList —
    key: parent view’s layout ID
    value: prefetch count ( number of views we want it to pre-inflate.)
  2. prefetchItemBindingMap —
    key: view’s layout ID
    value: ViewBindings
  3. prefetchChildViewTypeList —
    key: parent view’s layout ID
    value: child view’s layout ID and it’s prefetch count (number of child views we want to pre-inflate)

The prefetch count will depend upon the number of views visible on the UI initially + scrap views. You can also play with this number a bit as per your requirement. Caution: setting this value too high might impact the performance negatively.


Since we need the parent view to which the items will get attached, we will start prefetching on the call of onAttachedToRecyclerView() method.


Now let us see what this prefetchItemBinding() method does.


As we can see, this method first checks if the prefetchViewTypeListis empty or not. If not, it calls the initAsyncItemBinding() method for each iteration.

Now let’s see what the initAsyncItemBinding() method does:


In this method, we are inflating items asynchronously. It iterates as per the given count and stores the inflated ViewBindings in prefetchItemBindingMap with their respective layout ID as the key.

Then finally we will call prefetchChildItemBinding() method to inflate its children’s views if there are any. We need to call this method only once and that can be done for any parent. (here we are calling it for index 0 as it doesn’t matter to which parent the child gets attached)

Let’s see what this method does:


Before inflating child views, we have to pass some validations first. If any of the below conditions satisfies no child’s views will be inflated.

  1. If the adapter is not attached, there is no need to inflate any view.
  2. There is no parent view binding present for these children, no inflation will be done because we need parents to inflate the child.
  3. If there are no views to pre-inflate. (prefetchChildViewTypeList is empty)
  4. If there are no child views present for this parent.
  5. If this viewType of children is already inflated by some other parent view.

If none of the conditions satisfies, only then child views will be inflated. They will be inflated similarly by calling the initAsyncItemBinding method. One important and interesting thing to notice here is that if the child views have their own child views, this will get handled itself by the initAsyncItemBinding method.

You know what, all the heavy lifting is done🥳🎉 and now we just have to start using it with our custom adapter.

We will be using these two methods mentioned below to set the values in prefetchViewTypeList and prefetchChildViewTypeList .


In our custom adapter’s init block, we will be passing the values to these methods.


We will get pre-inflated views using getPrefetchedItemBinding() method. We have to pass parent and layout ID to this in arguments. First, it will check if there is any prefetched view in the prefetchItemBindingMap if not then it will simply inflate the view as per normal flow and return the ViewDataBiding.


Now the question is where will we call this method from?

This method will be simply called inside onCreateViewHolder() instead of inflating the item.


And thus do not have to inflate new items until the prefetchItemBindingMap gets empty and after that new items will get inflated as they would have if there were no PrefetchViewPool implemented.

And This is it!! We are done and it’s time for a victory dance.

I know all this still sounds magical🪄🤩

If you still don’t believe me then I’ve two things for you.

  1. Please visit a therapist because you have big trust issues😂
  2. I can lie to you but Android Profiler won’t. So, below are the results from CPU profiling.

Without PrefetchViewPool

With PrefetchViewPool

The traces in the above profiling starts when we set the data to adapter and call notifyDataSetChanged() and stops at onLayoutCompleted() .

Key points to notice in the above results:

  1. Load time reduced from 214.19ms to 118.35ms🔥
  2. If you carefully observe, you’ll notice yellow boxes inside RV OnLayout trace has practically been reduced to zero as no onCreateViewHolder() are being made after implementing PrefetchViewPool.

Link to Git repository —


Special mentions: 

Anshul Agarwal and Parth Gupta

You May Also Like…

Building Scalable A/B Testing Microservice in Go

Building Scalable A/B Testing Microservice in Go

Introduction If you are here, you probably are already familiar with A/B testing. Instead of defining what it is, let’s skip to the good part. Tech Design We wanted to build a micro-service for A/B experiments which can run at scale and can be used by other backend...

Blue/Green deployment for WinZO API Gateway

Blue/Green deployment for WinZO API Gateway

Introduction: There’s a lot that goes behind the scenes in creating India’s largest social gaming platform serving millions of users and handling outrageous amounts of requests every minute. In this ever evolving industry, there’s hundreds of new exciting games,...