Computer scienceMobileAndroidUser InterfaceLists

RecyclerView

16 minutes read

RecyclerView is a component for creating scrollable lists. It's used in almost every modern Android app and was introduced as a replacement for ListView, which has a lot of flaws. In this topic, you'll discover that RecyclerView isn't as scary as it might initially seem and learn how to use it in your projects.

About RecyclerView

RecyclerView was introduced with Android 5.0. It's available in the Support Library and can also be used with older Android versions.

The main idea of RecyclerView is to create dynamic lists. It's flexible, feature-rich, and efficient in terms of resource usage. It contains the word "Recycler" in its name for a reason: when the View is scrolled off the screen, it disappears but isn't destroyed. Instead, it will be reused by new items that are about to be shown on the screen. ListView can technically do this, too. But it needs the help of an Adapter with a ViewHolder, and this requires a lot of boilerplate code. RecyclerView's performance is much better, so it's great for working with large data sets. All you have to do is define how the items should look and supply the necessary data.

RecyclerView can host Views in several different ways: vertically, horizontally, or in a grid. A LayoutManager is used to arrange them. And the RecyclerView library provides three LayoutManagers out of the box: LinearLayoutManager, GridLayoutManager, and StaggeredGridLayoutManager. It's also possible to create your own LayoutManager or use a third-party one.

As explained, RecyclerView is used to display data. The way each item looks can be defined in the relevant XML files. And RecyclerView.Adapter is utilized to make a View for each item and fill it with your data. You may already be familiar with Adapters: they're also used with Spinner, GridView, ListView, etc.

Adding RecyclerView

To learn how to use this component, let's start by adding RecyclerView to a project. To achieve this, we first need to open an activity XML file and find the RecyclerView option in the Palette:

RecyclerView in Palette menu

We then need to drag it onto our layout and add the required project dependency by clicking OK in the dialog box that pops up:

Confirmation dialog for adding dependency

At this point, Android Studio will perform some actions in the background. And after a short while, we'll see a RecyclerView that takes up all the available space in our layout. We now need to specify an ID for the RecylerView, which will be required later.

As previously mentioned, a data source is needed to fill a RecyclerView with data. The data is usually drawn from the internet, but you can take it from whichever source you want.

We'll create a simple bank transaction generator as an example. A random number of transactions will be generated in a list and these will be dumped into a RecyclerView so they can be viewed on-screen. You can see the Transaction and Bank classes that we'll be using in the following code snippets.

The Transaction class:

class Transaction(
   var receiver: String,
   var account: String,
   var amount: String,
   var status: String
)

The Bank class that we will use to generate the random data:

package com.example.hs

import kotlin.random.Random

class Bank {

    private val receivers = listOf("FreshBurgers", "NewPost Delivery", "GameStore", "Hyperskill", "NearbyGroceries", "MyCellularProvider", "Coffee Home")
    private val accounts = listOf("Debit card", "Credit card")
    private val status = listOf<String>("Successful", "Failed")
    private val random = Random
    fun generateTransactions(): ArrayList<Transaction>{
        var list = ArrayList<Transaction>()
        for (i in 1..random.nextInt(7) + 1) {
            list.add(
                Transaction(getRandomReceiver(), getRandomAccount(), getRandomTransactionAmount(), getRandomStatus())
            )
        }
        return list
    }

    private fun getRandomReceiver(): String {
        return receivers.random()
    }

    private fun getRandomAccount(): String {
        return accounts.random()
    }

    private fun getRandomTransactionAmount(): String {
        return "$${random.nextInt(1, 100)}.00"
    }

    private fun getRandomStatus(): String {
        return status.random()
    }

}

Defining a layout

We now need to create a layout for our transactions. To do this, right-click the res/layout folder and choose New → Layout Resource File. Then give the file an appropriate name and hit OK:

Layout file creation window

A new layout will pop up on the screen — this is where we specify how an item should look. So, for our example, we'll opt for a RelativeLayout and populate it with transaction data including the receiver, the amount of money spent, the account, and whether it was successful. (Feel free to try out other layout options if you wish.)

The XML file is shown below:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/receiver_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="16dp"
        android:layout_marginTop="10dp"
        android:textSize="20sp"
        tools:text="Receiver" />

    <TextView
        android:id="@+id/account_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/receiver_tv"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        tools:text="Account" />

    <TextView
        android:id="@+id/amount_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="16dp"
        android:textSize="20sp"
        tools:text="$10.00" />

    <TextView
        android:id="@+id/status_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/amount_tv"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        tools:text="Successful" />

    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="10dp"
        android:layout_below="@id/account_tv"
        android:background="?android:attr/listDivider" />


</RelativeLayout>

And this is how it looks (with measurements included):

RecyclerView item layout diagram

It contains all the required placeholders. Next, we'll create an Adapter to help us fill the placeholders with some actual data.

Creating an Adapter

Every RecyclerView needs an Adapter — it will allow us to adapt the data we have generated to our layout. So let's create one!

We need to define a new Kotlin class for our Adapter, pass the data list as a parameter, and extend the RecyclerView.Adapter class. RecyclerView.Adapter requires us to provide a type, so we'll pass a custom ViewHolder to it (as you'll see later). A ViewHolder is responsible for describing an item View and the metadata about its place within the RecyclerView.

When we extend RecyclerView.Adapter, Android Studio will highlight the relevant line and indicate that the associated members need to be implemented. If we click the highlighted line and opt to implement all the available methods, the result should look similar to the code snippet shown below:

package com.example.hs

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class RecyclerAdapter(private var transactions: List<Transaction>) : RecyclerView.Adapter<>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ??? {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: ???, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

}

Note that ??? is present in the above example because we haven't created a custom ViewHolder class yet.

The next step is to delete the three TODO() lines. With that done, let's quickly show the purposes of the associated methods:

  • onCreateViewHolder() creates ViewHolders, as its name implies. ViewHolders are needed for all the items that will be shown in the RecyclerView. They're created for the number of items that can fit on the screen at any one time and will be reused.
  • onBindViewHolder() is used to bind the data from the item to the View. It gets called multiple times with the same set of holders to enable reuse.
  • getItemCount() allows RecyclerView to determine the total number of items in the list.

Now let's go back to our code and edit the getItemCount() method so that it returns the number of transactions:

override fun getItemCount(): Int {
    return transactions.size
}

As previously mentioned, we also need to build the custom ViewHolder class that will set the data in the ViewHolder:

class TransactionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val receiver = view.findViewById<TextView>(R.id.receiver_tv)
    val account = view.findViewById<TextView>(R.id.account_tv)
    val amount = view.findViewById<TextView>(R.id.amount_tv)
    val status = view.findViewById<TextView>(R.id.status_tv)
}

We can now replace ??? with the name of the custom ViewHolder class that we have created. The custom ViewHolder class should also be passed as a type argument to onBindViewHolder(), as shown below:

package com.example.hs

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class RecyclerAdapter(private var transactions: List<Transaction>) : RecyclerView.Adapter<RecyclerAdapter.TransactionViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {

    }

    override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {

    }

    override fun getItemCount(): Int {
        return transactions.size
    }

    class TransactionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val receiver = view.findViewById<TextView>(R.id.receiver_tv)
        val account = view.findViewById<TextView>(R.id.account_tv)
        val amount = view.findViewById<TextView>(R.id.amount_tv)
        val status = view.findViewById<TextView>(R.id.status_tv)
    }
}

At this point, we need to bind the data to the View in onBindViewHolder(). The View is set in the item layout (the item_transaction.xml file) and should be bound to the corresponding data from the data source:

override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
    var transaction = transactions[position]

    holder.receiver.text = transaction.receiver
    holder.account.text = transaction.account
    holder.amount.text = transaction.amount
    holder.status.text = transaction.status
}

The last method we need to implement is onCreateViewHolder():

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
    return TransactionViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false))
}

This method will probably look the same in any RecyclerView Adapters you create in the future. It's used to return the custom ViewHolder that we passed the View to. Looking at the above code snippet, you might be wondering, "what is LayoutInflater and what's actually happening here?" LayoutInflater is a class that can turn layouts into View objects, which is exactly what we need. The method responsible for this is inflate(). And we pass it three arguments: the ID of the layout resource we require, the root ViewGroup to use, and false, which specifies that an attachment should not be made to the parent. We are then able to receive a View object.

We've successfully created an Adapter!

Wiring things up

Now it's time to switch to our activity with the RecyclerView. We need to create a variable that will contain the required data and define a LayoutManager and Adapter for our RecyclerView:

package com.example.hs

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val data = Bank().generateTransactions()
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)

        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = RecyclerAdapter(data)
    }
}

Bear in mind that there's another way to set our RecyclerView's LayoutManager and Adapter. This eliminates the need to specify the ID of the RecyclerView on every line, so it might save you some time!

recyclerView.apply{
    layoutManager = LinearLayoutManager(this)
    adapter = RecyclerAdapter(data)
}

It's also possible to specify a LayoutManager in the XML file instead of doing it in your Kotlin code:

<androidx.recyclerview.widget.RecyclerView
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:orientation="vertical"
    ...>

And that's it! Everything should work now, so give it a try by running the application:

RecyclerView list in application

As you might have spotted, we've been a bit unlucky with random booleans, and every transaction has failed. But who cares about that? We've just created a fully functional RecyclerView!

Conclusion

This topic covered the basics of RecyclerView. You've learned about how RecylerView works and the benefits of using it. You also created an Adapter and a small RecyclerView project of your own. Time for some practice!

35 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo