Speeding up RecyclerViews

I recently had to deal with an issue where the scroll speed of a list of items in a RecyclerView was scrolling very slow. The view was usable, but you could notice the stutters and lag when using the application. I’ll go over a few things I attempted to speed up the RecyclerViews.

EDIT: There were some feedback about this article when I posted on the androiddev subreddit here. I wrote a follow up post about the solution I chose in this follow up post.

Before I get too deep into this post however, I have to mention that the main reason my RecyclerView was so slow as due to me displaying EditText elements in the RecyclerView. Render EditText elements are expensive, and since RecyclerViews “recycle” views as the user scrolls, the “onBindViewHolder” method is constantly called as the user scrolls, which causes the app to continuously re-render the already expensive EditText fields. So with that, I’ll start with the first tip…

1. Do not display EditText fields or any input fields in the ViewHolder

This was the first mistake that was made in the codebase. I wasn’t the original developer who began this project (although I took over the project at an early stage of development), but the previous developer made the decision to utilize RecyclerViews to render out various different ViewHolders dependent on the data that we received from the API. This resulted in more elegant code and probably more memory efficient application thanks to the nature of how RecyclerViews work, but resulted in a terrible user experience since the application had to continuously inflate the expensive EditText fields onto the screen. Therefore, RecyclerViews should not be utilized for any type of situations where you want to display a list of input fields.

The solution that I implemented was to remove the RecyclerView and simply loop through the input field data I received from the API, and dynamically inflate and add the input fields to the LinearLayout in my XML.

2. Make your onBindViewHolder as light as possible

RecyclerViews “recycle” views, meaning if there are a 100 rows, it doesn’t hold all 100 rows in memory. Instead, as the user scrolls, the RecyclerView removes the rows that aren’t displayed on screen, and then runs the code that inflates those rows again when the user scrolls back to the appropriate position.

The method that gets called to inflate the appropriate row is onBindViewHolder. Thus, this method is called constantly as the user scrolls through the screen, and therefore the code in this method should be as light as possible. The less efficient the code in the onBindViewHolder method is, the more performance hit you’ll take on your RecyclerView.

3. Prefetch the items in your RecyclerView

First thing if you haven’t, upgrade your support library to the latest version to at least v25.0. The reason being is that the Android team implemented something called prefetching in the RecyclerViews starting from v25.0 of the Support Library.

View story at Medium.com

Prefetching is explained in detail in the above Medium article, but the basic premise of the prefetch works like this. As the user scrolls through the items in your RecyclerView, rather than displaying the items as the user scrolls, the RecyclerView will prefetch the items up ahead as the user scrolls so that the item rows are ready to be displayed before it displays on the user’s screen.

The only disadvantage of this is that there’s a risk where you prefetch items that the user never scrolls to, thus wasting CPU cycle, but I think the benefits of prefetching items far outweigh the costs.

4. Preload all data that you need before you load them into your adapter

This is probably common sense but you want to make all your API calls and database queries for all your data before loading them up in yourRecyclerView.Adapter. This is to make your onBindViewHolder as light as possible. Think about it. If you’re making database queries constantly as the user scrolls through your RecyclerView, you’re taking an unnecessary performance hit. Just load up all your data and hold it in memory and then load it up on your RecyclerView.Adapter to avoid unnecessary performance hits.

5. Set setHasFixedSize(true) on your RecyclerView

If your design allows for it, in your RecyclerView, set setHasFixedSize(true). It’ll look something like this.

From the Google Docs:

https://developer.android.com/training/custom-views/optimizing-view.html

Another very expensive operation is traversing layouts. Any time a view calls requestLayout(), the Android UI system needs to traverse the entire view hierarchy to find out how big each view needs to be. If it finds conflicting measurements, it may need to traverse the hierarchy multiple times. UI designers sometimes create deep hierarchies of nested ViewGroup objects in order to get the UI to behave properly. These deep view hierarchies cause performance problems. Make your view hierarchies as shallow as possible.

Basically, whenever new items are inserted, moved, or removed, the size of the RecyclerView might change and in turn the size of any other view in the view hierarchy might change. This is expensive if this happens frequently. Avoid this unnecessary layout passes by setting setHasFixedSize(true) on your RecyclerView.

6. Set setHasStableIds(true) to your Adapter

There’s a method called setHasStableIds that you can call on your adapter. If you have a slow RecyclerView, try setting this to true like mAdapter.setHasStableIds(true). This is an optional optimization technique you can try to see if it speeds up your RecyclerView.

What this does is that it tells your Adapter that when you provide a ViewHolder, its id is unique and will not change. This prevents not-ideal situations where you might accidentally link the id to your item’s position. This way it doesn’t have to handle re-ordering of the entire adapter because it can tell if item X at position Y is the same as before and do less work.

7. If you’re using DataBinder, free your data binds up on onViewRecycled

This is a tip I got from reading this blog post

https://blog.workable.com/recyclerview-achieved-60-fps-workables-android-app-tips/

DataBinder is apparently a “thing” these days and if you’re using that, it’s a good idea to free up some unneeded resources. With DataBinding, it’s a good idea to remove onPropertyChangedCallbacks from our ViewModel and then clear the ViewModel itself from the binding.

 

I learned a lot about how to optimize the performance of my RecyclerViews during this little “exercise” in an attempt to speed up my client’s RecyclerView, even though I ended up replacing the RecyclerView with a more appropriate solution of simply inflating custom views onto an existing LinearLayout.

Hopefully this post can be of some help to those who are dealing with a slow RecyclerView.

About the Author Chris Jeon

Software developer currently focusing on Android development.