7.
RecyclerViews
Written by Darryl Bayliss
In this chapter, you’ll begin to build ListMaker. An app to help organize all of your to-do lists in one handy place.
Lists are a common visual design pattern in apps, they allow developers to group collections of information together. They also allow users to scroll through and interact with each item in the list.
An item in a list can range from a line of text to more complex content like a video with comments below it — a common style used in most social media apps.
In Android development, you implement lists using a class named RecyclerView. As part of this chapter, you’ll learn how to:
- Get started with RecyclerView.
- Set up a RecyclerView Adapter to populate a list with data.
- Set up a ViewHolder to handle the Layout of each item in the list.
Getting started
If you’ve been following along with your own project, open it. If not, don’t worry. Locate the projects folder for this chapter and open the Listmaker app inside the starter folder. The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.
With the Android Studio project open, examine the project structure. In particular, look at the following files:
- MainActivity.kt and MainFragment.kt: Located in the java folder.
- activity_main.xml and content_main.xml: Located in the res\layout folder.
Kotlin (.kt) files drive the logic of your app. MainActivity.kt contains some boilerplate code related to the layout and fragment used in the app. MainFragment.kt is a Fragment used within MainActivity.kt. The Fragment is where your Views will be placed. Don’t worry too much about what a Fragment is at the moment. You will learn more about them in Chapter 11.
In previous chapters, you used a single layout file to build the user interface. In this project, there are two layout files: main_activity.xml and main_fragment.xml.
Open main_fragment.xml. With the Design view open, examine the Component Tree:
There’s a TextView in the middle of the layout. You’re going to replace the TextView with a RecyclerView.
Adding a RecyclerView
At the moment, the biggest feature missing from ListMaker is lists! There isn’t any way to show a list, let alone the master list of lists. It’s like Inception, but…Listception instead. You’ll fix that by adding a RecyclerView.
With main_fragment.xml still open in the design view. Select the TextView and delete it.
Next, go to the Palette and click Common.
Click and drag a RecyclerView from the list of components into the middle of the Layout.
Once the RecyclerView is present in the layout move to the Attributes window and change the ID to lists_recyclerview. This lets you reference the RecyclerView in your Kotlin file.
Next, in the Constraint Widget in the Layout pane, click all of the plus symbols to create constraint connections against the edges of the Layout for the RecyclerView.
Set the margins for each connection to 0. This will make sure the RecyclerView is flush against the edges.
Underneath the Constraint Widget, set layout_width and layout_height to 0dp (match_constraint). This makes sure that it takes the full width and height.
The RecyclerView is now positioned correctly. In the next part, you’ll begin to use it.
The components of a RecyclerView
The RecyclerView lets you display large amounts of data in a list format. Each piece of data is treated as an item within the RecyclerView. In turn, each of these items makes up the entire contents of the RecyclerView.
RecyclerViews have two required components it uses to display a list of items, an Adapter and ViewHolders. The following diagram shows how these components work together:
Let’s break down the flow of each component:
- The
RecyclerViewasks theAdapterhow many items it has and for aViewHolderat a given position. - The
Adapterreaches into the pool of createdViewHolders it has. - Either a
ViewHolderis returned or a new one is created. - The
Adapterbinds theViewHolderto a data item at the given position. - The
ViewHolderis returned to theRecyclerViewfor display.
Adapters give the RecyclerView the data it wants to show. They have a clever way to calculate how many rows of data you want to show, which you’ll cover shortly.
ViewHolders are the visual containers for your item. Think of them as placeholders for each item in the table; this is where you tell the RecyclerView how each item should look.
As you scroll through a RecyclerView, instead of creating new ViewHolders, RecyclerView recycles ViewHolders that move off-screen and populates them with new data, ready to be shown at the bottom of the list.
This process repeats as you scroll through the RecyclerView. This recycling of ViewHolder to display list items helps to avoid jank in your app.
Note: Jank is a common term used to refer to dropped or missed frames while rendering. As an app user, you might have experienced stuttering while scrolling long lists. This is affectionately known as jank.
That concludes the whirlwind tour of RecyclerView. Now it’s time to get coding!
Hooking up a RecyclerView using ViewBinding
For this app, you’re going to access views from your layout differently. Using a technique called ViewBinding. ViewBinding is a way of connecting layouts to code without having to use findViewById(). Let’s see how this works.
Open up the app build.gradle. Then, within the Android block, add the following lines:
android {
...
buildFeatures {
viewBinding true
}
}
Here, you’re telling gradle to add the viewBinding feature to your app. Next, you need to start using data binding.
Before proceeding. Build the app to allow the view bindings to generate. You can do this by clicking the green hammer along the top of Android Studio. Once the build has finished, open MainFragment.kt and create a property to hold the binding at the top of the class:
private lateinit var binding: MainFragmentBinding
You use the lateinit keyword to tell the compiler that a MainFragmentBinding will be created sometime in the future.
Next, update onCreateView to link to the binding. Don’t worry too much about what this method does, you’ll learn more about it later on in Chapter 9.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = MainFragmentBinding.inflate(inflater, container, false)
return binding.root
}
The MainFragmentBinding object has been generated by Android Studio because ViewBinding is enabled. Android Studio will do this for every layout in your project, with the binding object containing properties for each View. Now you don’t need to call findViewById() to link your views to your code!
Let’s setup the RecyclerView with ViewBinding. Update onCreateView with the following:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = MainFragmentBinding.inflate(inflater, container, false)
// 1
binding.listsRecyclerview.layoutManager = LinearLayoutManager(requireContext())
// 2
binding.listsRecyclerview.adapter = ListSelectionRecyclerViewAdapter()
return binding.root
}
Here’s what you’re doing:
- You use the
bindingclass to accesslistsRecyclerView. This is the RecyclerView you created earlier inmain_fragment.xml. Then, you let the RecyclerView know what kind of layout to present your items in. This is similar to Layouts you use with your XML Layouts. You need something to arrange your items in a linear format. TheLinearLayoutManagerworks perfectly for this. You also specify that the layout manager needs aContextby usingrequireContext().
Note:
LinearLayoutManagerisn’t the only layout manager provided by Android. Android provides theGridLayoutManagerandStaggeredGridLayoutManager. You can read more about them over at https://developer.android.com/guide/topics/ui/layout/recyclerview#modifying-layout
- The Adapter for the RecyclerView is set, letting it know to use this Adapter to acquire its data to show, and the
ViewHoldersto use to populate data with.
You’ll notice an error showing in Android Studio. This is because ListSelectionRecyclerViewAdapter doesn’t exist. You’ll create this in the next part.
Setting up a RecyclerView Adapter
Right-click ui.main in the Project navigator. In the floating options that appear, hover over New. In the next set of options that appear, click Kotlin File/Class.
In the popup that appears, enter ListSelectionRecyclerViewAdapter for the Name and change the Kind drop-down to Class. Then, press the enter key.
Android Studio creates the class for you. Repeat the process and create a ViewHolder class too. Name this new class ListSelectionViewHolder.
You’re ready to turn these classes into recycling machines. Open ListSelectionViewHolder.kt and add a primary constructor to the class. This allows you to pass in the ViewBinding for the ViewHolder and have it extend RecyclerView.ViewHolder:
class ListSelectionViewHolder(val binding: ListSelectionViewHolderBinding) : RecyclerView.ViewHolder(binding.root) {
}
Notice that ListSelectionViewHolderBinding is in red as you haven’t created the layout yet. Open ListSelectionRecyclerViewAdapter.kt and extend the class to inherit from RecyclerView.Adapter<ListSelectionViewHolder>():
class ListSelectionRecyclerViewAdapter : RecyclerView.Adapter<ListSelectionViewHolder>() {
}
Here you pass in the type of ViewHolder you want the RecyclerView Adapter to use. This makes the RecyclerView aware of the type of ViewHolder it expects to use so you can reference it in a few methods you’ll implement shortly.
Notice that the name of the class is underlined in red. Move your mouse cursor over it, and Android Studio informs you why there’s an error.
Because this class inherits from RecyclerView.Adapter, it needs to implement additional methods so it knows what to do when used in conjunction with a RecyclerView.
With your cursor over the class name, press Option-Enter to get a selection of options.
Note: This keystroke assumes you’re using a Mac for Android development; however, Windows and Linux versions of Android Studio provide an equivalent shortcut through Alt-Enter.
Having trouble getting this to work? Alternatively, hover the cursor over the class name and press Control-I or select Code along the top toolbar of Android Studio, and click Implement Members
Click Implement Members, and a new window appears with options for various methods to implement. Since the Recycler Adapter needs each one, you’ll add them all.
Ensure onCreateViewHolder() is highlighted, then Shift-click on the bottom-most available member.
Finally, click OK and Android Studio does the rest of the work for you, by generating the methods needed for a RecyclerView Adapter.
Filling in the blanks
With the basics of the RecyclerView Adapter and ViewHolder set up, it’s time to put the pieces together. First, you need content for the RecyclerView to show. For now, you’ll add some mock titles to show off the RecyclerView.
You also need a layout for the ViewHolder so the RecyclerView knows how each item within it should look. Finally, you need to bind the titles to the ViewHolder at the right time depending on what position it has within the RecyclerView. You’ll use ViewBinding for this.
You’ll implement the mock list titles first. In ListSelectionRecyclerViewAdapter.kt, add the following new variable at the top of the class:
val listTitles = arrayOf("Shopping List", "Chores", "Android Tutorials")
Here, you create an array of strings to use as the list titles. In future chapters, you’ll change this to something more sophisticated — but for now, an array will do.
getItemCount() determines how many items the RecyclerView has. You want the size of the array to match the size of the RecyclerView, so you return that.
In getItemCount(), you return the size of the array, like so:
override fun getItemCount(): Int {
return listTitles.size
}
Your Adapter now knows how many items to display on the screen. Next, you need to create the layout needed for the ViewHolder to display each item in the RecyclerView.
Creating the ViewHolder
In the Project navigator on the left, right-click on the layout folder and create a new Layout resource file:
In the new window that appears, type list_selection_view_holder for the File name; this serves as the name of the layout when created.
The Root Element defines the first tag in the Layout. For this Layout, you’ll use a LinearLayout, so type LinearLayout into the text field.
Click OK at the bottom of the window. Android Studio opens your new Layout, ready for you to add the Views you want the ViewHolder to contain. You need two TextViews here: one to tell you the position of the list in the RecyclerView, and one to tell you the name of the list.
With the Design window open, drag a TextView onto the Layout.
In the Attributes window to the right of Android Studio, change the ID of the TextView to itemNumber. Also, change the layout_width and layout_height attributes to wrap_content, and remove the placeholder text from the text attribute:
To ensure the text isn’t sitting too close to the edge of the screen, you need to give it space on its left edge. You do this by setting the layout_margin parameter, inside the Layout_Margin attribute to add padding to this TextView.
First, find the layout_margin attribute and click the arrow next to it to reveal a drop-down for each of the parameters. If you can’t find it, it may be hidden in the All attributes dropdown.
In the layout_margin text field, type 16dp. This tells the TextView to pad itself by 16 density pixels (dp) on all sides.
Note: A density pixel is a unit of measurement Android uses to layout your view relative to the size of the device screen. Because devices have many different screen sizes, using absolute pixels isn’t feasible as screens will render differently from device to device. To learn more, review the Android Developer Documentation: https://developer.android.com/guide/practices/screens_support.html.
With that done, repeat the process for the first TextView, by dragging another TextView into the Layout. Place it underneath the first TextView.
Change the ID of the second TextView to itemString. Change the layout_width and layout_height to wrap_content. Remove the placeholder text from the text attribute and change the layout_margin parameter in the layout_margin attribute to 16dp.
You’re nearly done with this Layout — there’s only one more thing to do.
Currently, the TextViews are laid out in a vertical orientation. However, horizontal orientation is better suited for this app, so you need to change some attributes on the LinearLayout the Layout uses.
Click the LinearLayout in the Component Tree window:
In the Attributes Window, click the drop-down button on the orientation attribute and select horizontal.
Change the layout_height attribute to wrap_content to make the ViewHolder only as big as it needs to be. Then set layout_width to match_parent:
You’re ready to use the Layout. Build the app to allow the view bindings to generate. Open ListSelectionViewHolder.kt and import the binding class.
Next, open ListSelectionRecyclerViewAdapter.kt and change onCreateViewHolder() to use the view binding generated for the ViewHolder:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListSelectionViewHolder {
// 1
val binding = ListSelectionViewHolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
// 2
return ListSelectionViewHolder(binding)
}
This method does two things:
-
First, it creates a LayoutInflater object from the parent context and then uses the binding class to inflate itself. This creates a new binding class that allows you to bind data to the view.
Note:
LayoutInflateris a system utility used to instantiate (or “inflate”) a layout XML file into its corresponding View objects. -
A
ListSelectionViewHolderobject is created, passing in the binding. Finally, the ViewHolder is returned from the method.
Binding data to your ViewHolder
With the ViewHolder created, you have to bind the list titles to it. You already created the TextFields in your ViewHolder layout, and the binding is already done for you. All that’s left is to assign the right value to each TextView.
With ListSelectionRecyclerViewAdapter.kt open. Edit onBindViewHolder() so it sets a value for each of the TextViews on the ViewHolder:
override fun onBindViewHolder(holder: ListSelectionViewHolder, position: Int) {
holder.binding.itemNumber.text = (position + 1).toString()
holder.binding.itemString.text = listTitles[position]
}
For each call of onBindViewHolder(), you take the TextViews you created in the ViewHolder and populate them with their position in the list and the name of the list from the listTitles array. This is called repeatedly as you scroll through the RecyclerView.
The moment of truth
Finally! You can see the fruits of your labors. Click the Run App button at the top of Android Studio and see what happens.
Fantastic, you now have a list of titles and the position they hold in the RecyclerView. Great job!
Key Points
You’ve just completed your first steps to build ListMaker! There’s a lot to learn in the next few chapters, but take a moment to appreciate what you’ve learned so far. You’ve learned:
-
How to setup ViewBinding to reference Views instead of findViewById().
-
How to setup a RecyclerView, a layout manager, and the adapter.
-
How to create a ViewHolder for use with the RecyclerView.
-
How to populate each row of a RecyclerView using a ViewHolder.
Where to go from here?
There are many moving pieces required to use RecyclerView to display a list of data. However, don’t be afraid to use them, they’re an essential construct for creating Android apps that provide fluid and intuitive user experiences. They are as common as Buttons and TextViews.
If you want to learn more about RecyclerView, review the documentation on the developer website https://developer.android.com/guide/topics/ui/layout/recyclerview.html. It dives deeper into the inner workings of RecyclerView and describes how to animate changes to list items.
If you’re still looking for more, check out the tutorial on the Ray Wenderlich site https://www.raywenderlich.com/170075/android-recyclerview-tutorial-kotlin which shows how to use different LayoutManagers, and how to swipe to delete items in your list.