How to Implement a RecyclerView in Android Kotlin with Item Click Handling
The JetBrains/kotlin repository provides a minimal, fully-functional example in its Gradle-plugin integration tests that demonstrates how to bind data to a RecyclerView and handle item clicks efficiently using the view tag pattern.
If you are looking for a straightforward way to implement a RecyclerView in Android Kotlin with basic item click handling, the official Kotlin repository contains a concise reference implementation. This example, located within the kapt2/android-dbflow test project, demonstrates the essential pattern for creating an adapter, binding views, and responding to user interactions without unnecessary complexity.
Project Location in the Kotlin Repository
The complete working example resides in the integration test resources for the Kotlin Gradle plugin. You can find all relevant files in libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/android-dbflow/app/src/main/java/mobi/porquenao/poc/kotlin/ui/. This self-contained module includes the adapter, activity, and layout files necessary for a functional RecyclerView implementation, making it easy to copy into your own projects.
Layout Structure
Main Activity Layout
The activity_main.xml file defines a vertical RecyclerView inside a LinearLayout. The RecyclerView uses @+id/list as its identifier and includes vertical scrollbars for user feedback.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/AppToolbar"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:scrollbars="vertical"/>
</LinearLayout>
Item Layout
Each list item uses a CardView containing a clickable LinearLayout container. The container uses android:clickable="true" and a selectable item background to provide visual feedback, while TextView elements display the title and date.
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="30sp"/>
<TextView
android:id="@+id/date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="18sp"/>
</LinearLayout>
</android.support.v7.widget.CardView>
Adapter Implementation with Click Handling
The MainAdapter class extends RecyclerView.Adapter and demonstrates the ViewHolder pattern essential for efficient list rendering. It manages a mutable list of Item objects retrieved from ItemRepository.getAll().
Inside onBindViewHolder, the adapter implements click handling by storing the data object in the container view's tag and attaching a shared OnClickListener. This approach avoids creating new listener instances for every item and provides direct access to the clicked item's data.
class MainAdapter : RecyclerView.Adapter<MainAdapter.ViewHolder>() {
private val mItems: MutableList<Item> = ItemRepository.getAll()
private val mOnClickListener = View.OnClickListener { v ->
val item = v.tag as Item
item.updatedAt = Calendar.getInstance()
item.save()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(R.layout.main_item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mItems[position]
val date = item.updatedAt.timeInMillis
val color = "#" + date.toString().substring(7)
holder.card.setCardBackgroundColor(Color.parseColor(color))
holder.title.text = color
holder.date.text = DateFormat.format("hh:mm:ss", Date(date))
holder.container.apply {
tag = item // store the data object
setOnClickListener(mOnClickListener) // handle click
}
}
override fun getItemCount(): Int = mItems.size
fun add() {
val item = Item()
mItems.add(0, item)
item.save()
notifyItemInserted(0)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val card: CardView = view.findViewById(R.id.card)
val container: LinearLayout = view.findViewById(R.id.container)
val title: TextView = view.findViewById(R.id.title)
val date: TextView = view.findViewById(R.id.date)
}
}
Key implementation details:
- ViewHolder pattern: The
ViewHolderclass caches references tocard,container,title, anddateviews to eliminate repetitivefindViewByIdcalls. - Click mechanism: The adapter stores the
Itemobject inholder.container.tagbefore attachingmOnClickListener, allowing the listener to retrieve the item viav.tag as Item. - Dynamic updates: The
add()method demonstrates how to insert new items at position 0 and notify the adapter withnotifyItemInserted(0).
Activity Setup
The MainActivity initializes the RecyclerView with a LinearLayoutManager and attaches the MainAdapter. It also demonstrates dynamic list modification through the options menu, calling listAdapter.add() and scrolling to the top when users select the add action.
class MainActivity : BaseActivity() {
private lateinit var listAdapter: MainAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<RecyclerView>(R.id.list).apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this@MainActivity)
listAdapter = MainAdapter()
adapter = listAdapter
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
listAdapter.add()
findViewById<RecyclerView>(R.id.list).smoothScrollToPosition(0)
return true
}
}
Understanding the Click Handling Pattern
This implementation uses the view tag mechanism to associate data with UI elements. In onBindViewHolder, the code executes holder.container.tag = item to bind the data object directly to the view hierarchy. When a user taps the container, the shared listener retrieves this object through the view's tag property, updates the timestamp, persists changes via item.save(), and refreshes the UI with notifyDataSetChanged().
This pattern is memory-efficient because it uses a single OnClickListener instance for all items rather than creating anonymous classes within the binding method.
Summary
- Location: Find the complete example in
libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/android-dbflow/app/src/main/java/mobi/porquenao/poc/kotlin/ui/within the JetBrains/kotlin repository. - Adapter: The
MainAdapterdemonstrates proper ViewHolder caching and implements click handling via view tags to avoid memory overhead. - Layouts: XML files define a scrollable RecyclerView and clickable CardView items with selectable item backgrounds.
- Pattern: Storing data objects in view tags enables efficient item click retrieval without requiring adapter position calculations in the listener.
Frequently Asked Questions
How does the RecyclerView adapter handle item clicks in this Kotlin example?
The adapter stores each item object in the container view's tag property during onBindViewHolder. A shared OnClickListener instance retrieves the item from v.tag, allowing direct access to the clicked data without calculating adapter positions or creating new listener objects per row.
Where can I find the source files for this RecyclerView implementation?
All source files are located in the JetBrains/kotlin repository under libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kapt2/android-dbflow/app/src/main/java/mobi/porquenao/poc/kotlin/ui/. This includes MainAdapter.kt, MainActivity.kt, and the corresponding XML layout files in the res/layout/ directory.
Why does the adapter use notifyDataSetChanged() instead of notifyItemChanged() in the click listener?
The example uses notifyDataSetChanged() for simplicity to refresh the entire list when an item's timestamp updates. In production code, you should prefer notifyItemChanged(position) to trigger a single item refresh and maintain animations, but the example prioritizes clarity over optimization.
Can I use this code in my own Android project?
Yes, you can copy the adapter, activity, and layout files into any Android project using AndroidX or AppCompat dependencies. Note that the example uses android.support.v7.widget packages, so you may need to migrate to androidx equivalents depending on your project's configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →