Scrollable list is one of the most common feature in modern mobile apps. In Android we have two popular ways to achieve this. We can use either ListView or RecyclerView. Do you know exactly how they work and which one should you choose for your projects? Maybe you’re struggling with understanding when to use each of them. Let me help you with that. This article is gonna cover all of these questions and even more!
One last thing before we start
✉️ Android Dev Newsletter
If you enjoy learning about Android like I do and want to stay up to date with the latest, worth reading articles, programming news and much more, consider subscribing to my newsletter👇
🎙 Android Talks Podcast
If you’re a Polish speaker and want to listen to what I have to say about Android, architecture, security and other interesting topics, check out my podcast👇
ListView vs RecyclerView is one of the most common questions you can be asked at an Android Dev Interview, so it is worth to read this article and to test both of them on your own to check out what they can do
The first key difference between the ListView and RecyclerView is when where they introduced. Well, ListView has been with us since the very beginning (API 1). It was always a way for Android Developers to display scrollable lists. It wasn’t the best solution though, because the list had to create a new view item every time user scrolled and as you know view creation is a very expensive thing to do. The bigger the list, the more resource hungry would the application be. It was a pain in the a** for Android Developers before RecyclerView came out.
You could implement ViewHolder in the ListView and reuse the views like in RecyclerView, but it was more like optional thing to do, while in RecyclerView it’s the default way of writing the adapter class.
In 2014, after a long time of waiting, RecyclerView, CardView and Design Support Library were introduced. The idea of RecyclerView was very simple. Instead of creating a new view every time user scrolled to the next positions, just create views once and recycle/reuse them. This was achievable by using ViewHolder. Besides that, there is also LayoutManager, ItemDecoration, ItemAnimator and more. Let’s talk in more detail about them…
So what’s RecyclerView exactly?
RecyclerView is a library for list drawing that essentially provides a fixed-size window to load a large dataset into. When the views get out of scope it recycles the views created before. It is a standalone support library, so in order to use it in your project you have add it separately to your app module’s build.gradle file. As of 2021 it is a part of Jetpack, so all of the info about how to include it in your project can be found here: https://developer.android.com/jetpack/androidx/releases/recyclerview
A ViewHolder describes an item view and metadata about its place within the RecyclerView. It allows making our list scroll smoothly by storing views references. That’s why it’s called a ViewHolder. By doing this calling findViewById(int id) occurs much less times, instead of calling it for the entire dataset on each view binding. It can be also implemented in ListView, but it’s not mandatory. The RecyclerView’s adapter forces us to use the ViewHolder pattern. The adapter actions are separated to creation — onCreateViewHolder(…) and to updating the view — onBindViewHolder(…). If you implement the adapter for RecyclerView then it is compulsory to provide a ViewHolder. In ListView it’s optional, but not using it causes inefficient scrolling and much higher memory usage.
LayoutManager is responsible for laying out the row views. It allows us to choose how the views should be displayed and scrolled. Besides that it is telling the ViewHolder when to recycle a child view once it’s out of scope.
When using a ListView, you can only create a vertical-scrolling list, so it’s not really a flexible solution for us. If you wanted to create a grid before RecyclerView was introduced, you had to use another UI component for that — GridView.
Now, that we have the RecyclerView everything is easier. There are 3 main LayoutManagers you can choose from:
LinearLayoutManager — handles vertical and horizontal linear layouts
GridLayoutManager — for managing grid layouts with columns/spans
StaggeredLayoutManager — for handling grids, but each item can have different size
The most important thing about the RecyclerView is the Adapter. Like any other adapters it’s responsibility is to bind datasets to views that are going to be displayed in the window. A typical adapter is just an iterator that has getCount() method for knowing about limitations. Usually it depends on the dataset size to create new views for binding. This kind of adapters are also used in ListView or ViewPager for example.
RecyclerView, as mentioned before, uses the ViewHolder pattern by default. In this case the RecyclerView adapter is not creating new views every time, but instead it creates only ViewHolders that hold the inflated views to reuse them later.
ViewHolders are created and referenced with specific item viewType (Int), not the position. Because of that it is easier for the adapter to find views to reuse and you can add as many view types as you want, by creating new ViewHolder type classes.
The onBindViewHolder(…) method is used to bind views every time a new list element will appear while scrolling, so it will be called a lot of times during scrolling the list, so make sure it is well written to avoid memory and performance issues (for example adding onClick events, interfaces, etc.)
The last thing you have to take care about is the getItemCount() method. This one will be called several times to know the size limit of the list. It generally returns size of the dataset that is used in the adapter.
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter’s data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
All ItemDecorations are drawn in the order they were added, before the item views (in
onDraw() and after the items (in
onDrawOver(Canvas, RecyclerView, RecyclerView.State).
This class defines the animations that take place on items as changes are made to the adapter. Subclasses of ItemAnimator can be used to implement custom animations for actions on ViewHolder items. The RecyclerView will manage retaining these items while they are being animated, but implementors must call
dispatchAnimationFinished(ViewHolder) when a ViewHolder’s animation is finished. In other words, there must be a matching
dispatchAnimationFinished(ViewHolder) call for each
By default, RecyclerView uses
ListView does not support ItemAnimator, so in order to add some animations to it you have to create custom animations in “anim” folder and manually attach it to a specific views.
ListView has one method that you can call in order to tell the adapter (which extends BaseAdapter) that some of the items changed. This is notifyDataSetChanged(). As you can see, this is not very much for us developers. Shouldn’t there be more than just that?
That’s why RecyclerView.Adapter now includes a lot of them, so you can have full control of your items. For example notifyDataSetChanged(), notifyItemChanged(int position), notifyItemInserted(int position) any many more.. Be sure to test it out in different scenarios to fully understand it.
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
DiffUtil uses Eugene W. Myers’s difference algorithm to calculate the minimal number of updates to convert one list into another. Myers’s algorithm does not handle items that are moved so DiffUtil runs a second pass on the result to detect items that were moved.
The callback helps us to skip manual handling of notifying an adapter. Instead of using different methods for each type of notifications as
notifyItemChanged() we just can use this simple method:
AsyncListDiffer require the list to not mutate while in use. This generally means that both the lists themselves and their elements (or at least, the properties of elements used in diffing) should not be modified directly. Instead, new lists should be provided any time content changes. It’s common for lists passed to DiffUtil to share elements that have not mutated, so it is not strictly required to reload all data to use DiffUtil.
This class is a convenience wrapper around
AsyncListDiffer that implements Adapter common default behavior for item access and counting.
While using a LiveData<List> is an easy way to provide data to the adapter, it isn’t required — you can use
submitList(List) when new lists are available.
Here’s a quick example:
As you can, we just need to define onCreateViewHolder, onBindViewHolder and DiffUtil.ItemCallback (which is the simplest type of DiffUtil.Callback) methods. From now on it is enough to use adapter.submitList(newItems) method of ListAdapter to update the data
ListView has been with us for a long time, but as you can see its deputy RecyclerView is beyond ListView’s capacity. It is more efficient by default, has simpler animations and adapter actions and overall using this new API is quite nice. So if you still wonder what should you choose for your lists in future apps — go for RecyclerView. I hope I helped with understanding the differences between these two. Be sure to test them out on your own and see how different mechanisms work