Android RecyclerView Tutorial with Kotlin
In this Android RecyclerView tutorial, learn how to use Kotlin to display datasets of a large or unknown size! By Kevin D Moore.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android RecyclerView Tutorial with Kotlin
30 mins
- Getting Started
- Obtaining The API Keys
- RecyclerView 101
- Why Use a RecyclerView?
- RecyclerView and Layouts
- Creating the RecyclerView
- Laying Out RecyclerView Items
- Adapters for RecyclerView
- Keeping Hold of Your Views
- Assembling The Pieces
- Hooking up the Adapter and RecyclerView
- Adding Scrolling Support
- Layout Changes
- Using ItemTouchHelper
- Where to Go From Here?
Laying Out RecyclerView Items
Phase two involves creating a custom layout for the item you want the RecyclerView to use. It works the same way as creating a custom layout for a ListView or Gridview.
Go to your layout folder and create a new layout with the name recyclerview_item_row
, making sure the root element is a ConstraintLayout
.
Note: ConstraintLayout is Android’s latest layout and is an ultra-uber Layout. It’s similar to a RelativeLayout but can have chained items, gridlines and barriers. Check out more about ConstraintLayouts.
Note: ConstraintLayout is Android’s latest layout and is an ultra-uber Layout. It’s similar to a RelativeLayout but can have chained items, gridlines and barriers. Check out more about ConstraintLayouts.
In your new layout, change the ConstraintLayout to look like this:
<android.support.constraint.ConstraintLayout 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:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
Add the following XML elements as children of the ConstraintLayout:
<ImageView
android:id="@+id/itemImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toTopOf="@+id/itemDate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.74" />
<TextView
android:id="@+id/itemDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_marginTop="8dp"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@+id/itemDescription"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemImage"
tools:text="Some date" />
<TextView
android:id="@+id/itemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemDate" />
No rocket science here: You declared a few views as children of your layout. Now you can use them in your adapter.
Adapters for RecyclerView
Right-click on the com.raywenderlich.galacticon folder, select New ‣ Kotlin File ‣ Class, name it RecyclerAdapter and select Class for Kind.
Make the class extend RecyclerView.Adapter as in the following:
class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.PhotoHolder>() {
}
Android Studio will prompt you to import the RecyclerView class. Click on RecylerView and press Option-Return (or Alt-Enter on a PC) and choose Import. Since you’re extending a class that has required methods, Android Studio will underline your class declaration with a red squiggle.
To resolve this, click on the line of code to insert your cursor and press Option-Return (or Alt-Enter on a PC) to bring up a context menu. Select Implement Methods:
Select all three methods and press OK to implement the suggested methods:
These methods are the driving force behind your RecyclerView adapter. Note there is still a compiler error for the moment. That’s because your adapter and the required methods are defined using your ViewHolder class, PhotoHolder
, which doesn’t exist just yet. You’ll get to define your ViewHolder and see what each required method does soon. Hang tight!
As with every adapter, provide the corresponding view a means of populating items and deciding how many items there should be.
Previously, a ListView’s or GridView’s onItemClickListener
managed item clicks. A RecyclerView doesn’t provide methods like this because its focus is ensuring the position and management of the items within.
The job of listening for actions is now the responsibility of the RecyclerView item and its children. This may seem like more overhead, but in return, you get fine-grained control over how your item’s children can act.
At the top of your RecyclerAdapter class, add a variable photos
in the primary constructor to hold your photos:
private val photos: ArrayList<Photo>
So it looks like:
class RecyclerAdapter(private val photos: ArrayList<Photo>) : RecyclerView.Adapter<RecyclerAdapter.PhotoHolder>() {
Nice job, Commander! Your adapter now knows where to look for data. Soon you’ll have an ArrayList of photos filled with the finest astrophotography!
Next, populate the stubbed methods that Android Studio added.
The first method, getItemCount()
, should be familiar if you’ve worked with ListViews or GridViews.
The adapter will work out how many items to display. In this case, you want the adapter to show every photo you’ve downloaded from NASA’s API. Add update getItemCount()
to the following:
override fun getItemCount() = photos.size
Next, you’re going to exploit the ViewHolder
pattern to make an object that holds all your view references.
Keeping Hold of Your Views
To create a PhotoHolder for your view references, you’ll create a nested class in your adapter. You’ll add it here rather than in a separate class because its behavior is tightly coupled with the adapter.
Add the following code at the bottom of the RecyclerAdapter class:
//1
class PhotoHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener {
//2
private var view: View = v
private var photo: Photo? = null
//3
init {
v.setOnClickListener(this)
}
//4
override fun onClick(v: View) {
Log.d("RecyclerView", "CLICK!")
}
companion object {
//5
private val PHOTO_KEY = "PHOTO"
}
}
Here’s what the code above does:
- Make the class extend RecyclerView.ViewHolder, allowing the adapter to use it as as a ViewHolder.
- Add a reference to the view you’ve inflated to allow the ViewHolder to access the ImageView and TextView as an extension property. Kotlin Android Extensions plugin adds hidden caching functions and fields to prevent the constant querying of views.
- Initialize the
View.OnClickListener
. - Implement the required method for
View.OnClickListener
since ViewHolders are responsible for their own event handling. - Add a key for easy reference to the item launching the RecyclerView.
You should still have a compiler errors with onBindViewHolder
and onCreateViewHolder
. Change the p0
argument on onBindViewHolder
to holder
and the p1
to position
.
override fun onBindViewHolder(holder: RecyclerAdapter.PhotoHolder, position: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
Then change the p0
argument on onCreateViewHolder
to parent
and the p1
to be viewType
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.PhotoHolder {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
Build and run the app again. It’ll look nearly the same because you haven’t told the RecyclerView how to associate the PhotoHolder with a view.
Assembling The Pieces
Sometimes there are no ViewHolders available. In this scenario, RecylerView will ask onCreateViewHolder()
from RecyclerAdapter to make a new one. You’ll use the item layout — PhotoHolder — to create a view for the ViewHolder.
You could add the inflate code to onCreateViewHolder()
. However, this is a nice opportunity to show a cool Kotlin feature called Extensions.
First, add a new Kotlin file named Extensions.kt to the project and add the following new extension function to the new file:
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
Replace the TODO("not implemented")
line between the curly braces in onCreateViewHolder()
with the following:
val inflatedView = parent.inflate(R.layout.recyclerview_item_row, false)
return PhotoHolder(inflatedView)
Here you inflate the view from its layout and pass it in to a PhotoHolder. The parent.inflate(R.layout.recyclerview_item_row, false)
method will execute the new ViewGroup.inflate(...)
extension function to inflate the layout.
Now the object holds onto those references while it’s recycled, but there are still more pieces to put together before launching your rocket.
Start a new activity by replacing the log in PhotoHolder’s onClick
with this code:
val context = itemView.context
val showPhotoIntent = Intent(context, PhotoActivity::class.java)
showPhotoIntent.putExtra(PHOTO_KEY, photo)
context.startActivity(showPhotoIntent)
This grabs the current context of your item view and creates an intent to show a new activity on the screen, passing the photo object you want to show. Passing the context object into the intent allows the app to know what activity it’s leaving.
Next, add this method inside PhotoHolder
:
fun bindPhoto(photo: Photo) {
this.photo = photo
Picasso.with(view.context).load(photo.url).into(view.itemImage)
view.itemDate.text = photo.humanDate
view.itemDescription.text = photo.explanation
}
This binds the photo to the PhotoHolder, giving your item the data it needs to work out what it should show.
It also adds the suggested Picasso import, which is a library that makes it simpler to get images from a given URL.
The last piece of the PhotoHolder assembly will tell it how to show the right photo at the right moment. It’s the RecyclerAdapter’s onBindViewHolder
, and it lets you know a new item will be available on screen and the holder needs some data.
Add the following code inside the onBindViewHolder()
method:
val itemPhoto = photos[position]
holder.bindPhoto(itemPhoto)
Here, you’re passing in a copy of your ViewHolder and the position where the item will show in your RecyclerView, and calling bindPhoto(...)
.
That takes care of the assembly. Use the position where your ViewHolder will appear to grab the photo out of your list and then pass it to your ViewHolder.
Step three is complete! Now for the final stage before blast off.