Categories
Android App Development

How To: RecyclerView with a Kotlin-Style Click Listener in Android

In this article, we add a click listener to a RecyclerView on Android. Advanced language features of Kotlin make it far easier than it has been with Java. However, you need to understand a few core concepts of the Kotlin language.

To get started with the RecyclerView, follow the steps in the previous article or check out the finished project on GitHub.

Updated on December 15th, 2020: the solution projects on GitHub have been migrated to the latest versions and dependencies. Most importantly, the new solutions now also use Jetpack View Bindings instead of Kotlin synthetics. The text in this article is still the original.

Updated on July 4th, 2019: Google is transitioning the additional libraries to AndroidX. Nothing changes in terms of behavior with regards to our example. I’ve updated the source code examples on GitHub to use AndroidX instead of the Android Support libraries.

Higher-Order Functions

To understand the most elegant way of adding click handlers to Android, you need to know about higher-order functions and how Kotlin handles those. I’ll spend a short time explaining this, before applying it to the RecyclerView.

Primary Constructors

Let’s start with a class that defines parameters. In Kotlin, you can write the primary constructor directly into the class definition. All parameters are automatically available as properties within that class. Note that the keyword constructor is optional; you can skip it.

class ClassWithConstructorProperties constructor (var a: Int, var b: Int) {
    fun calculate() : Int {
        return a + b;
    }
}

As you can see in the code above, the function calculate() directly accesses the properties a and b. We didn’t need to define properties and copy the values from parameters. This is something Kotlin has done for us.

To create an instance of the class and to call the function, use the following code:

// Create new class instance
var calcTest = ClassWithConstructorProperties(10, 20)
// Print calculation results
Log.d("Tests", "Calculation result: " + calcTest.calculate())

Function Parameters

The Kotlin documentation describes: higher-order functions take another function as parameter or return a function. Let’s experiment with that.

private fun testFunctionParameters(performCalculation: (Int, Int) -> Int) {
    Log.d("Tests", "Calculation result: " + performCalculation(1, 2))
}

This function takes a function parameter. We give it the name performCalculation. The name is needed to call the function from within our code.

Kotlin needs to know & check what types of parameters the function expects, and what type it returns. Therefore, we specify that the caller needs to supply a function that has two Int parameters. Additionally, the function has to return an Int.

Our testFunctionParameters() function simply executes the supplied function and logs the results.

Lambdas

How can we call such a function that expects function parameters? Either, we simply supply a function that is built with a matching structure. Another option is to use lambda expressions.

We use lambdas as anonymous functions. They don’t have a name and are used like an expression. They’re declared and used immediately.

Semantically, the lambda function is surrounded by curly braces { }. It has a structure of: parameters > code / return type. Let’s try that:

// Call a function, supplying a lambda to the function parameter
testFunctionParameters( {a : Int, b : Int -> a + b } )

We call our higher-order function testFunctionParameters(). As parameter, we supply a lambda expression – as is clear by the surrounding { }.

The contents of labmda follow the definition from above. First, we specify the parameters. Our lambda function takes two parameters, which we call a and b. Both are of type Int .

After the -> we write the implementation. In this case, it’s a simple addition of a + b . The return type (Int) is inferred automatically by Kotlin: the operation of adding to Integers returns an Integer.

Click Handler in RecyclerView

With the knowledge of function parameters and lambda expressions, we can start adding a click handler to our RecyclerView.

Android RecyclerView - Click Listener - Flow

In its constructor, the Adapter requires a reference to the data source and a click handler. Whenever it’s instructed by the RecyclerView to bind a new ViewHolder, it assigns the click listener as well as the correct values from the data source to the view.

When the user later clicks on an item in the RecyclerView, the click listener is executed. As parameter, the listener gets the item that was clicked, to react accordingly.

Traditionally, you would have implemented this with an Interface and Listeners. In Kotlin, you can greatly simplify the code through function parameters and lambda expressions.

For further reference, I also recommend two related articles from Antonio Leiva: Writing a RecyclerView Adapter in Kotlin + How lambdas work in Kotlin.

Extend the Adapter

So far, the primary constructor of our Adapter only had one parameter: the data list. We now need a second parameter: the click listener function. As you can guess based on the article so far, this is a function parameter.

class PartAdapter (val partItemList: List<PartData>, val clickListener: (PartData) -> Unit) :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {

The clickListener parameter is a val, meaning that it’s constant in our Adapter. The function parameter requires a function with the following structure:

  • Parameter: an instance of a PartData. That’s the data class we defined earlier. The data model (first parameter of the class) is a list of these PartDatas.
  • Return type: Unit. The click handler doesn’t need to return anything to the caller.

To recap: after this step, our PartAdapter class has a new property called clickListener, which is of a specific function parameter type.

ViewHolder & Click Listener

We want the individual items in the list to handle click events. These items are managed by the ViewHolder. In our example, we called the view holder: PartViewHolder. Whenever the RecyclerView assigns (new) content to the item, our Adapter calls bind() of the specific ViewHolder responsible for the item view.

Therefore, bind() is the logical place to also ensure the item sends back the correct item data whenever it’s clicked. We add a second parameter to the bind() function: a function parameter for the click listener.

class PartViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(part: PartData, clickListener: (PartData) -> Unit) {
        itemView.tv_part_item_name.text = part.itemName
        itemView.tv_part_id.text = part.id.toString()
        itemView.setOnClickListener { clickListener(part)}
    }
}

Within the bind() function, we assign the click listener to the view. A lambda expression takes care of this. In it, we call the function parameter and supply the instance of the PartData  as parameter.

To recap: whenever the View item is clicked, Android calls the click listener. We supply a lambda expression. Within this lambda expression, we call the click listener with the relevant data class instance. We got the click listener through a function parameter.

Assign Click Listener to the View Holder

We’ve now configured the ViewHolder to assign the click listener to the view. The next step is to send the click handler instance to our bind()  function.

Add the clickListener as parameter when we call bind() . The clickListener is part of the primary constructor of our PartAdapter class. Therefore, it’s available as a property within all methods of this class.

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    // Populate ViewHolder with data that corresponds to the position in the list
    // which we are told to load
    (holder as PartViewHolder).bind(partItemList[position], clickListener)
}

Handle Clicks in MainActivity

The Adapter and the ViewHolder are ready to process click events. The last step is to handle clicks. We created and configured the RecyclerView in MainActivity.

Handling clicks on list items is usually a longer code segment – you start a request, open a different activity, update the UI, etc. Therefore, we create a separate function in MainActivity to process the clicks:

private fun partItemClicked(partItem : PartData) {
    Toast.makeText(this, "Clicked: ${partItem.itemName}", Toast.LENGTH_LONG).show()
}

To illustrate the concept, we simply show a toast. Additionally, we include the itemName property of our PartData data class into the message. As we defined in our function parameter structure, the instance of the item that was clicked is sent as parameter.

Register the Click Handler Function with the Adapter

Finally, we provide our click listener implementation to the Adapter. Extend the configuration line in onCreate() with a second parameter:

rv_parts.adapter = PartAdapter(testData, { partItem : PartData -> partItemClicked(partItem) })

Again, we use a lambda expression. The first part of the lambda is the parameter: in our case, we call it partItem, and it’s of type PartData (our custom Kotlin-style data class).

After the ->, we add the code part. Instead of implementing the code right there, we call our new handler function instead. Even though we’re in a lambda expression, we can call the function like from any other place of our code. Additionally, we send the partItem as parameter to our partItemClicked() function.

Our structure of the function parameter type requires that the code has a return type of Unit (see section “Click Handler in RecyclerView” in the article). Our partItemClicked() function doesn’t return a value, so it’s OK to assign the function here.

To recap: we use a lambda expression for the new function parameter of our Adapter. In the lambda, we configure a new handler function. It executes whenever the user clicks an item in the RecyclerView.

Test Clicks on RecyclerView Items

Run the app on your device and click on one of the list items in the RecyclerView. A toast shows the itemName property of the clicked item.

Android RecyclerView with a Click Listener, developed with Kotlin

What’s great about this Kotlin-style approach is that you don’t need to worry about defining and implementing interfaces. With lambda functions and function parameters, you can simply wire up a click listener with the RecyclerView. In conclusion, this is an elegant approach for one of the most common use-cases!

Download the finished example app on GitHub. I’ve slightly extended this solution. In addition, it navigates to a second activity when an item is clicked in the RecyclerView. The app passes the ID of the clicked item as an extra through the intent.

Article Series