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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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.

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:

  1. Make the class extend RecyclerView.ViewHolder, allowing the adapter to use it as as a ViewHolder.
  2. 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.
  3. Initialize the View.OnClickListener.
  4. Implement the required method for View.OnClickListener since ViewHolders are responsible for their own event handling.
  5. 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.