Use RecyclerView to display a scrollable list

If you think about the apps you commonly use on your phone, almost every single app has at least one list. The call history screen, the contacts app, and your favorite social media app all display a list of data. As shown in the screenshot below, some of these apps display a simple list of words or phrases, where others display more complex items such as cards that include text and images. No matter what the content is, displaying a list of data is one of the most common UI tasks in Android.

To help you build apps with lists, Android provides the RecyclerView. RecyclerView is designed to be very efficient, even with large lists, by reusing, or recycling, the views that have scrolled off the screen. When a list item is scrolled off the screen, RecyclerView reuses that view for the next list item about to be displayed. That means, the item is filled with new content that scrolls onto the screen. This RecyclerView behavior saves a lot of processing time and helps lists scroll more smoothly.

In the sequence shown below, you can see that one view has been filled with data, ABC. After that view scrolls off the screen, RecyclerView reuses the view for new data, XYZ.

In this codelab, you will build the Affirmations app. Affirmations is a simple app that displays ten positive affirmations as text in a scrolling list. Then, in the follow-up codelab, you will take it a step further, add an inspiring image to each affirmation, and polish the app UI.

Prerequisites

  • Create a project from a template in Android Studio.
  • Add string resources to an app.
  • Define a layout in XML.
  • Understand classes and inheritance in Kotlin (including abstract classes).
  • Inherit from an existing class and override its methods.
  • Use the documentation on developer.android.com for classes provided by the Android framework.

What you'll learn

  • How to use a RecyclerView to display a list of data.
  • How to use adapters to customize how an individual list item looks.

What you'll build

  • An app that displays a list of affirmation strings using a RecyclerView.

What you need

  • A computer with Android Studio installed.
  • Android Studio 4.1 or later installed. The instructions in this codelab assume that you have this or a later version of Android Studio.

Important: The instructions in this codelab assume that you are using Android Studio 4.1 and later. Please update to the latest version of Android Studio. You can still do the codelab otherwise, but will have to adapt the instructions for your situation on your own .

Create an Empty Activity project

  1. Start a new Kotlin project in Android Studio using the Empty Activity template.
  2. Enter Affirmations as the app Name, com.example.affirmations as the Package name, and choose API Level 19 as the Minimum SDK.
  3. Click Finish to create the project.

Add Material dependencies to your project

The Affirmations app uses Material Components and themes from the Material Design Components library. Older versions of Android Studio (prior to version 4.1) do not have the required dependency included by default for projects created from the templates. Add this Material library dependency to your project, if it's not already present.

  1. In the Project window, open Gradle Scripts > build.gradle (Module: app). There are two build.gradle files, one for the project and one for the app. Make sure you select the correct one in the app module.
  2. In build.gradle, locate the dependencies block near the bottom of the file.
  3. Locate the line that reads

implementation 'androidx.appcompat:appcompat:1.2.0'

and change it to

implementation 'com.google.android.material:material:1.2.0'

  1. Re-sync the project by clicking the Sync Now prompt that appears when you edit the gradle file.

The next step in creating the Affirmations app is to add resources. You will add the following to your project.

  • String resources to display as affirmations in the app.
  • A source of data to provide a list of affirmations to your app.

Add Affirmation strings

  1. In the Project window, open app > res > values > strings.xml. This file currently has a single resource which is the name of the app.
  2. In strings.xml, add the following affirmations as individual string resources. Name them affirmation1, affirmation2, and so on.

Affirmations text

I am strong.
I believe in myself.
Each day is a new opportunity to grow and be a better version of myself.
Every challenge in my life is an opportunity to learn from.
I have so much to be grateful for.
Good things are always coming into my life.
New opportunities await me at every turn.
I have the courage to follow my heart.
Things will unfold at precisely the right time.
I will be present in all the moments that this day brings.

The strings.xml file should look like this when you're done.

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

Now that you have added string resources, you can reference them in your code as R.string.affirmation1 or R.string.affirmation2.

Create a new package

Organizing your code logically helps you and other developers understand, maintain, and extend it. In the same way that you can organize paperwork into files and folders, you can organize your code into files and packages.

What is a package?

  1. In Android Studio, in the Project window (Android), take a look at your new project files under app > java for the Affirmations app. They should look similar to the screenshot below, which shows three packages, one for your code (com.example.affirmations), and two for test files (com.example.affirmations (androidTest) and com.example.affirmations (test)).

  1. Notice that the name of the package consists of several words separated by a period.

There are two ways in which you can make use of packages.

  • Create different packages for different parts of your code. For example, developers will often separate the classes that work with data and the classes that build the UI into different packages.
  • Use code from other packages in your code. In order to use the classes from other packages, you have to explicitly import them. For example, you have already used import statements for classes such as TextView (import android.widget.TextView) and mathematical functions, such as sqrt (import kotlin.math.sqrt).

In the Affirmations app, in addition to importing Android and Kotlin classes, you will also organize your app into several packages. Even when you don't have a lot of classes for your app, it is a good practice to use packages to group classes by functionality.

Naming packages

A package name can be anything, as long as it is globally unique; no other published package anywhere can have the same name. Because there are millions of packages, and coming up with random unique names is hard, programmers use conventions to make it easier to create and understand package names.

  • The package name is usually structured from general to specific, with each part of the name in lowercase letters and separated by a period. Important: The period is just part of the name. It does not indicate a hierarchy in code or mandate a folder structure!
  • Because internet domains are globally unique, it is a convention to use a domain, usually yours or the domain of your business, as the first part of the name.
  • You can choose the names of packages to indicate what's inside the package, and how packages are related to each other.
  • For code examples like this one, com.example followed by the name of the app is commonly used.

Here are some examples of predefined package names and their contents:

  • kotlin.math - Mathematical functions and constants.
  • android.widget - Views, such as TextView.

Create a package

  1. In Android Studio, in the Project pane, right-click app > java > com.example.affirmations and select New > Package.

  1. In the New Package popup, notice the suggested package name prefix. The suggested first part of the package name is the name of the package you right-clicked. While package names do not create a hierarchy of packages, reusing parts of the name is used to indicate a relationship and organization of the content!
  2. In the popup, append model to the end of the suggested package name. Developers often use model as the package name for classes that model (or represent) the data.

  1. Press Enter. This creates a new package under the com.example.affirmations (root) package. This new package will contain any data-related classes defined in your app.

Create the Affirmation data class

In this task, you'll create a class called Affirmation. An object instance of Affirmation represents one affirmation and contains the resource ID of the string with the affirmation.

  1. Right-click on the com.example.affirmations.model package and select New > Kotlin File/Class.

  1. In the popup, select Class and enter Affirmation as the name of the class. This creates a new file called Affirmation.kt in the model package.
  1. Make Affirmation a data class by adding the data keyword before the class definition. This leaves you with an error, because data classes must have at least one property defined.

Affirmation.kt

package com.example.affirmations.model

data class Affirmation {
}

When you create an instance of Affirmation, you need to pass in the resource ID for the affirmation string. The resource ID is an integer.

  1. Add a val integer parameter stringResourceId to the constructor of the Affirmation class. This gets rid of your error.
package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

Create a class to be a data source

Data displayed in your app may come from different sources (e.g. within your app project or from an external source that requires connecting to the internet to download data). As a result, data may not be in the exact format that you need. The rest of the app should not concern itself with where the data originates from or in what format it is originally. You can and should hide away this data preparation in a separate Datasource class that prepares the data for the app.

Since preparing data is a separate concern, put the Datasource class in a separate data package.

  1. In Android Studio, in the Project window, right-click app > java > com.example.affirmations and select New > Package.
  2. Enter data as the last part of the package name.
  3. Right click on the data package and select new Kotlin File/Class.
  4. Enter Datasource as the class name.
  5. Inside the Datasource class, create a function called loadAffirmations().

The loadAffirmations() function needs to return a list of Affirmations. You do this by creating a list and populating it with an Affirmation instance for each resource string.

  1. Declare List<Affirmation> as the return type of the method loadAffirmations().
  2. In the body of loadAffirmations(), add a return statement.
  3. After the return keyword, call listOf<>() to create a List.
  4. Inside the angle brackets <>, specify the type of the list items as Affirmation. If necessary, import com.example.affirmations.model.Affirmation.
  5. Inside the parentheses, create an Affirmation, passing in R.string.affirmation1 as the resource ID as shown below.
Affirmation(R.string.affirmation1)
  1. Add the remaining Affirmation objects to the list of all affirmations, separated by commas. The finished code should look like the following.

Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation


class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

[Optional] Display the size of the Affirmations list in a TextView

To verify that you can create a list of affirmations, you can call loadAffirmations() and display the size of the returned list of affirmations in the TextView that comes with your Empty Activity app template.

  1. In layouts/activity_main.xml, give the TextView that comes with your template an id of textview.
  2. In MainActivity in the onCreate() method after the existing code, get a reference to textview.
val textView: TextView = findViewById(R.id.textview)
  1. Then add code to create and display all the affirmations. Create a Datasource, call loadAffirmations(), get the size of the returned list, convert it to a string, and assign it as the text of textView.
textView.text = Datasource().loadAffirmations().size.toString()
  1. Run your app. The screen should look as shown below.

  1. Delete the code you just added in MainActivity.

In this task, you'll be setting up a RecyclerView to display the list of Affirmations.

There are a number of pieces involved in creating and using a RecyclerView. You can think of them as a division of labor. The diagram below shows an overview, and you will learn more about each piece as you implement it.

  • item - One data item of the list to display. Represents one Affirmation object in your app.
  • Adapter - Takes data and prepares it for RecyclerView to display.
  • ViewHolders - A pool of views for RecyclerView to use and reuse to display affirmations.
  • RecyclerView on Screen

Add a RecyclerView to the layout

The Affirmations app consists of a single activity named MainActivity, and its layout file is called activity_main.xml. First, you need to add the RecyclerView to the layout of the MainActivity.

  1. Open activity_main.xml (app > res > layout > activity_main.xml)
  2. If you are not already using it, switch to Split view.

  1. Delete the TextView.

The current layout uses ConstraintLayout. ConstraintLayout is ideal and flexible when you want to position multiple child views in a layout. Since your layout only has a single child view, RecyclerView, you can switch to a simpler ViewGroup called FrameLayout that should be used for holding a single child view.

  1. In the XML, replace ConstraintLayout with FrameLayout. The completed layout should look as shown below.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</FrameLayout>
  1. Switch to Design view.
  2. In the Palette, select Containers, and find the RecyclerView.
  3. Drag a RecyclerView into the layout.
  4. If it appears, read the Add Project Dependency popup and click OK. (If the popup doesn't appear, no action is needed.)
  5. Wait for Android Studio to finish and the RecyclerView to appear in your layout.
  6. If necessary, change the layout_width and layout_height attributes of the RecyclerView to match_parent so that the RecyclerView can fill the whole screen.
  7. Set the resource ID of the RecyclerView to recycler_view.

RecyclerView supports displaying items in different ways, such as a linear list or a grid. Arranging the items is handled by a LayoutManager. The Android framework provides layout managers for basic item layouts. The Affirmations app displays items as a vertical list, so you can use the LinearLayoutManager.

  1. Switch back to Code view. In the XML code, inside the RecyclerView element, add LinearLayoutManager as the layout manager attribute of the RecyclerView, as shown below.
app:layoutManager="LinearLayoutManager"

To be able to scroll through a vertical list of items that is longer than the screen, you need to add a vertical scrollbar.

  1. Inside RecyclerView, add an android:scrollbars attribute set to vertical.
android:scrollbars="vertical"

The final XML layout should look like the following:

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>
  1. Run your app.

The project should compile and run without any issues. However, only a white background is displayed in your app because you are missing a crucial piece of code. Right now, you have the source of data and the RecyclerView added to your layout, but the RecyclerView has no information on how to display the Affirmation objects.

Implement an Adapter for the RecyclerView

Your app needs a way to take the data from Datasource, and format it so that each Affirmation can be displayed as an item in the RecyclerView.

Adapter is a design pattern that adapts the data into something that can be used by RecyclerView. In this case, you need an adapter that takes an Affirmation instance from the list returned by loadAffirmations(), and turns it into a list item view, so that it can be displayed in the RecyclerView.

When you run the app, RecyclerView uses the adapter to figure out how to display your data on screen. RecyclerView asks the adapter to create a new list item view for the first data item in your list. Once it has the view, it asks the adapter to provide the data to draw the item. This process repeats until the RecyclerView doesn't need any more views to fill the screen. If only 3 list item views fit on the screen at once, the RecyclerView only asks the adapter to prepare those 3 list item views (instead of all 10 list item views).

In this step, you'll build an adapter which will adapt an Affirmation object instance so that it can be displayed in the RecyclerView.

Create the Adapter

An adapter has multiple parts, and you'll be writing quite a bit of code that's more complex than what you've done in this course so far. It's okay if you don't fully understand the details at first. Once you have completed this whole app with a RecyclerView, you'll be able to better understand how all the parts fit together. You'll also be able to reuse this code as a base for future apps that you create with a RecyclerView.

Create a layout for items

Each item in the RecyclerView has its own layout, which you define in a separate layout file. Since you are only going to display a string, you can use a TextView for your item layout.

  1. In res > layout, create a new empty File called list_item.xml.
  2. Open list_item.xml in Code view.
  3. Add a TextView with id item_title.
  4. Add wrap_content for the layout_width and layout_height, as shown in the code below.

Notice that you don't need a ViewGroup around your layout, because this list item layout will later be inflated and added as a child to the parent RecyclerView.

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Create an ItemAdapter class

  1. In Android Studio in the Project pane, right-click app > java > com.example.affirmations and select New > Package.
  2. Enter adapter as the last part of the package name.
  3. Right-click on the adapter package and select New > Kotlin File/Class.
  4. Enter ItemAdapter as the class name, finish, and the ItemAdapter.kt file opens.

You need to add a parameter to the constructor of ItemAdapter, so that you can pass the list of affirmations into the adapter.

  1. Add a parameter to the ItemAdapter constructor that is a val called dataset of type List<Affirmation>. Import Affirmation, if necessary.
  2. Since the dataset will be only used within this class, make it private.

ItemAdapter.kt

import com.example.affirmations.model.Affirmation

class ItemAdapter(private val dataset: List<Affirmation>) {

}

The ItemAdapter needs information on how to resolve the string resources. This, and other information about the app, is stored in a Context object instance that you can pass into an ItemAdapter instance.

  1. Add a parameter to the ItemAdapter constructor that is a val called context of type Context. Position it as the first parameter in the constructor.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

}

Create a ViewHolder

RecyclerView doesn't interact directly with item views, but deals with ViewHolders instead. A ViewHolder represents a single list item view in RecyclerView, and can be reused when possible. A ViewHolder instance holds references to the individual views within a list item layout (hence the name "view holder"). This makes it easier to update the list item view with new data. View holders also add information that RecyclerView uses to efficiently move views around the screen.

  1. Inside the ItemAdapter class, before the closing curly brace for ItemAdapter, create an ItemViewHolder class.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder()
}
  • Defining a class inside another class is called creating a nested class.
  • Since ItemViewHolder is only used by ItemAdapter, creating it inside ItemAdapter shows this relationship. This is not mandatory, but it helps other developers understand the structure of your program.
  1. Add a private val view of type View as a parameter to the ItemViewHolder class constructor.
  2. Make ItemViewHolder a subclass of RecyclerView. ViewHolder and pass the view parameter into the superclass constructor.
  3. Inside ItemViewHolder, define a val property textView that is of type TextView. Assign it the view with the ID item_title that you defined in list_item.xml.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

Override adapter methods

  1. Add the code to extend your ItemAdapter from the abstract class RecyclerView.Adapter. Specify ItemAdapter.ItemViewHolder as the view holder type in angle brackets.
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

You will see an error because you need to implement some abstract methods from RecyclerView.Adapter.

  1. Put your cursor on ItemAdapter and press Command+I (Control+I on Windows). This shows you the list of methods you need to implement: getItemCount(), onCreateViewHolder(), and onBindViewHolder().

  1. Select all three functions using Shift+click and click OK.

This creates stubs with the correct parameters for the three methods as shown below.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    TODO("Not yet implemented")
}

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

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

You should see no more errors. Next you need to implement those methods so that they do the correct things for your app.

Implement getItemCount()

The getItemCount() method needs to return the size of your dataset. Your app's data is in the dataset property that you are passing into the ItemAdapter constructor, and you can get its size with size.

  1. Replace getItemCount() with this:
override fun getItemCount() = dataset.size

It's a more concise way of writing this:

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

Implement onCreateViewHolder()

The onCreateViewHolder()method is called by the layout manager to create new view holders for the RecyclerView (when there are no existing view holders that can be reused). Remember that a view holder represents a single list item view.

The onCreateViewHolder() method takes two parameters and returns a new ViewHolder.

  • A parent parameter, which is the view group that the new list item view will be attached to as a child. The parent is the RecyclerView.
  • A viewType parameter which becomes important when there are multiple item view types in the same RecyclerView. If you have different list item layouts displayed within the RecyclerView, there are different item view types. You can only recycle views with the same item view type. In your case, there is only one list item layout and one item view type, so you don't have to worry about this parameter.
  1. In the onCreateViewHolder() method, obtain an instance of LayoutInflater from the provided context (context of the parent). The layout inflater knows how to inflate an XML layout into a hierarchy of view objects.
val adapterLayout = LayoutInflater.from(parent.context)
  1. Once you have a LayoutInflater object instance, add a period followed by another method call to inflate the actual list item view. Pass in the XML layout resource ID R.layout.list_item and the parent view group. The third boolean argument is attachToRoot. This argument needs to be false, because RecyclerView adds this item to the view hierarchy for you when it's time.
val adapterLayout = LayoutInflater.from(parent.context)
       .inflate(R.layout.list_item, parent, false)

Now adapterLayout holds a reference to the list item view (from which we can later find

child views like the TextView).

  1. In onCreateViewHolder(), return a new ItemViewHolder instance where the root view is adapterLayout.
return ItemViewHolder(adapterLayout)

Here is the code for onCreateViewHolder() so far.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    // create a new view
    val adapterLayout = LayoutInflater.from(parent.context)
        .inflate(R.layout.list_item, parent, false)

    return ItemViewHolder(adapterLayout)
}

Implement onBindViewHolder()

The last method you need to override is onBindViewHolder(). This method is called by the layout manager in order to replace the contents of a list item view.

The onBindViewHolder() method has two parameters, an ItemViewHolder previously created by the onCreateViewHolder() method, and an int that represents the current item position in the list. In this method, you will find the right Affirmation object from the data set based on position.

  1. Inside onBindViewHolder(), create a val item and get the item at the given position in the dataset.
val item = dataset[position]

Finally, you need to update all the views referenced by the view holder to reflect the correct data for this item. In this case, there is only one view: the TextView within ItemViewHolder. Set the text of the TextView to display the Affirmation string for this item.

  1. With an Affirmation object instance, you can find the corresponding string resource ID by calling item.stringResourceId. However, this is an integer and you need to find the mapping to the actual string value.

In the Android framework, you can call getString() with a string resource ID, and it will return the string value associated with it. getString() is a method in the Resources class, and you can get an instance of the Resources class through the context.

That means you can call context.resources.getString() and pass in a string resource ID. The resulting string can be set as the text of the textView in the holder ItemViewHolder. In short, this line of code updates the view holder to show the affirmation string.

holder.textView.text = context.resources.getString(item.stringResourceId)

The completed onBindViewHolder() method should look like this.

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text =  context.resources.getString(item.stringResourceId)
}

Here is the finished adapter code.

ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

    /**
     * Return the size of your dataset (invoked by the layout manager)
     */
    override fun getItemCount() = dataset.size
}

Now that you've implemented the ItemAdapter, you need to tell the RecyclerView to use this adapter.

Modify the MainActivity to use a RecyclerView

To finish, you need to use your Datasource and ItemAdapter classes to create and display items in the RecyclerView. You do this in MainActivity.

  1. Open MainActivity.kt.
  2. In MainActivity, go to the onCreate() method. Insert this new code after the call to setContentView(R.layout.activity_main).
  3. Create an instance of Datasource, and call the loadAffirmations() method on it. Store the returned list of affirmations in a val named myDataset.
val myDataset = Datasource().loadAffirmations()
  1. Create a variable called recyclerView and use findViewById()to find a reference to the RecyclerView within the layout.
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
  1. To tell the recyclerView to use the ItemAdapter class you created, create a new ItemAdapter instance. ItemAdapter expects two parameters: the context (this) of this activity, and the affirmations in myDataset.
  2. Assign the ItemAdapter object to the adapter property of the recyclerView.
recyclerView.adapter = ItemAdapter(this, myDataset)
  1. Since the layout size of your RecyclerView is fixed in the activity layout, you can set the setHasFixedSize parameter of the RecyclerView to true. This setting is only needed to improve performance. Use this setting if you know that changes in content do not change the layout size of the RecyclerView.
recyclerView.setHasFixedSize(true)
  1. When you are done, the code for MainActivity should be similar to the following.

MainActivity.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

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

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}
  1. Run your app. You should see a list of affirmation strings displayed on screen.

Congratulations! You've just created an app that displays a list of data with RecyclerView and a custom adapter. Take some time to look over the code you created, and understand how the different pieces work together.

This app has all the required pieces to display your affirmations, but it's not quite ready for production. The UI could use some improvement. In the next codelab, you'll improve your code, learn how to add images to the app, and polish the UI.

The solution code for this codelab is in the project and module shown below. Note that some of the Kotlin files are in different packages, as indicated by the package statement at the start of the file.

res/values/strings.xml

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

affirmations/data/Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation


class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

affirmations/model/Affirmation.kt

package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

affirmations/MainActivty.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

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

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}

src/main/res/layout/activty_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</FrameLayout>

src/main/res/layout/list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  • RecyclerView widget helps you display a list of data.
  • RecyclerView uses the adapter pattern to adapt and display the data.
  • ViewHolder creates and holds the views for RecyclerView.
  • RecyclerView comes with built in LayoutManagers. RecyclerView delegates how items are laid out to LayoutManagers.

To implement the adapter:

  • Create a new class for the adapter, for example, ItemAdapter.
  • Create a custom ViewHolder class that represents a single list item view. Extend from RecyclerView.ViewHolder class.
  • Modify the ItemAdapter class to extend from the RecyclerView.Adapter class with the custom ViewHolder class.
  • Implement these methods within the adapter: getItemsCount(), onCreateViewHolder(), and onBindViewHolder().