Improve UI Performance with Async RecyclerView Layout Loading

RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very complex. The mentioned complexity may affect UI performance, especially when you have many different types of ViewHolders.
I had this problem in one of my projects. I had to load 10+ complex Items in my RecyclerView and run some animation at the same time. When you do layout inflation in runtime your app should render frames in about 16ms to achieve 60 frames per second. It is very difficult to achieve when you load RecyclerView. Average inflating time for RecyclerView item in my project was around 30ms and when we inflate RecyclerView with 10 items we get 300ms frame time and our FPS drops significantly.
How to find out if you have a problem? Usually when your FPS drops lower than 30 you can easily notice is when you are using your app. The signs may be flickering, freezing or unresponsiveness. Another way can be an integration of different FPS meters to your project or you can use Android Studio Profiler. Open CPU Profiler -> select “CPU profiling mode” as “Trace System Calls” and press “record”.

You can see that we have a frame which takes 138 ms and almost all of it was taken by “RV OnLayout”. That’s our RecyclerView is trying to create/inflate its ViewHolders. A possible solution is to implement async layout loading.
There has been some challenges in moving to the async inflation
- Android guidelines recommend using databinding to avoid writing extra code in onBindViewHolder. I decided to follow that approach but faced the first problem — AsyncLayoutInflater (mainly used for async loading of UI) doesn’t support databinding.
- The second problem — preserving the order of the data set when RecyclerView is loaded. But how to achieve that if some of items can be loaded faster than others?
To solve these problems I’ve created a custom class AsyncCell. It extends FrameLayout which serves as “dummy item” before the actual view is loaded. When our target view is loaded we add it as a child to the initially loaded FrameLayout. AsyncCell has “createDataBindingView” method which should be overriden in order to use databinding solving problem number one.
The RecyclerViewAdapter implementation is not really different from usual. The main difference is that now we have to instantiate AsyncCell for each ViewHolder type. You can see in my example I have 2 types of ViewHolder and I created 2 classes: SmallItemCell and LargeItemCell which encapsulate databinding creation logic. If you are not going to use databinding you can override only layoutId variable in AsyncCell.
In OnCreateViewHolder I create ViewHolder and pass my new LargeItemCell/SmallItemCell as a view to it’s constructor and call “inflate” method to initiate async loading.
The last thing is to call “bindWhenInflated” method inside “OnBindViewHolder” and pass there a function which will set databinding variables in my case or it can have logic which will update cell UI when inflation is completed.
When I finished the implementation I got the following result:

You can see that the maximum frame time now is ~29ms (it was ~140 before). and we just replaced the adapter class “RecyclerViewAdapter” to “RecyclerViewAsyncAdapter”. You can see the full project in GitHub:
Of course your requirements for RecyclerView can be different. You may not use databinding. But it shows you the idea of how it can be done if you feel that your business case needs it.
For more performance optimizations check out