Android ListView VS. RecyclerView

Share on facebook
Share on google
Share on twitter
Share on linkedin

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!

FUN FACT

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 🙂


Availability

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.

NOTE

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.


Developer’s redemption

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


ViewHolder

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

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
03 recyclerviewer basic

GridLayoutManager – for managing grid layouts with columns/spans
Android CardView Example With Gridlayout


StaggeredLayoutManager – for handling grids, but each item can have different size

device 2015 02 11 101312


Adapter

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.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
 // return new ViewHolder reference here based on the viewType
}


NOTE

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.


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
   if (holder is CustomViewHolder) {
     // bind data to the views of CustomViewHolder
   }
   ...
}


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.)

@Override
public int getItemCount() {
 if (dataset == null)
    return 0;

 return dataset.size();
}


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.


ItemDecoration

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).

For example, if you want to add divider between RecyclerView items, you can use ItemDecoration to achieve this – DividerItemDecoration to be specific.

ListView does not support ItemDecoration, so you have to figure it out on your own.


ItemAnimator

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 animateAppearance()animateChange() animatePersistence(), and animateDisappearance() call.

By default, RecyclerView uses DefaultItemAnimator.

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.


Notifying Adapter

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?

YUP!

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.


Extras


DiffUtil.Callback

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.

It can be used to calculate updates for a RecyclerView Adapter. See ListAdapter and AsyncListDiffer which can simplify the use of DiffUtil on a background thread.

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.

class DiffUtilCallback(
    private val oldItems: List,
    private val newItems: List
) : DiffUtil.Callback() {

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldItems[oldItemPosition].id == newItems[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // It works properly if Item is a data class
        // Otherwise, we should check if all fields of the items are the same
        return oldItems[oldItemPosition] == newItems[newItemPosition]
    }

    override fun getOldListSize() = oldItems.size
    override fun getNewListSize() = newItems.size
}


The callback helps us to skip manual handling of notifying an adapter. Instead of using different methods for each type of notifications as notifyItemInserted()notifyItemRemoved() or notifyItemChanged() we just can use this simple method:


fun renderItems(newItems: List){
    val diffResult: DiffUtil.DiffResult = DiffUtil.calculateDiff(DiffUtilCallback(oldItems, newItems))
    diffResult.dispatchUpdatesTo(this) // this : RecyclerView.Adapter
}

NOTE

DiffUtilListAdapter, and 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.



ListAdapter

RecyclerView.Adapter base class for presenting List data in a RecyclerView, including computing diffs between Lists on a background thread.

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:

class CustomAdapter : ListAdapter<Item, ItemViewHolder>(DiffUtilCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpotViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
        return ItemViewHolder(view)
    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.bind(currentList[position])
    }

    object DiffUtilCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item) = oldItem.id == newItem.id
        override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem == newItem
    }
}


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


Summary

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 😀

Leave a Reply

Your email address will not be published. Required fields are marked *