How To Build A Todo List In Kotlin Part 2: Implementing A RecyclerView

Following up on part 2 which demonstrates how to create your Android app and configure Kotlin, we’ll begin building the heart and soul of a Todo List application - the list!

Data Model

Let’s begin by defining our model. We’re going to create a simple class that has two fields, one for description and one for whether or not it’s completed. Here is how this class will look in Kotlin:

	data class Task(var description: String, var completed: Boolean = false) : Serializable

You may not believe it, but that’s all we need. Let’s talk about what’s happening here:

  • A data class is a special class in Kotlin that provides you with default behaviors for all your Object methods like toString() hashCode() equals() and copy(). Read more here.
  • Kotlin allows for default constructors to be defined right with the class name.
  • Kotlin allows for default parameters. So in this case, we have a constructor that can be used as Task("Description") and it will default to incomplete, or we can call it with Task("Description", true) to set the initial value of the completed boolean.
  • We’ve had our class implement Serializable. In this simple app, we’re just going to save the data to a text file instead of over complicating it with SQLite.

RecyclerView.Adapter

We can start out by first defining the XML layout for one of our list items. We’ll simply use a TextView and a CheckBox to mark completion:

	<?xml version="1.0" encoding="utf-8"?>
	<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    android:padding="8dp">

	    <TextView
	        android:layout_width="wrap_content"
	        android:layout_height="wrap_content"
	        android:textAppearance="?android:attr/textAppearanceMedium"
	        android:text="Medium Text"
	        android:id="@+id/task_description"
	        android:layout_alignParentTop="true"
	        android:layout_alignParentLeft="true"
	        android:layout_alignParentStart="true"
	        android:layout_toLeftOf="@+id/task_completed"
	        android:layout_toStartOf="@+id/task_completed"
	        android:textColor="@android:color/black"
	        android:layout_alignParentBottom="false" />

	    <CheckBox
	        android:layout_width="wrap_content"
	        android:layout_height="wrap_content"
	        android:id="@+id/task_completed"
	        android:layout_alignBottom="@+id/task_description"
	        android:layout_alignParentRight="true"
	        android:layout_alignParentEnd="true"
	        android:layout_alignParentTop="true" />
	</RelativeLayout>

Next, we need to define our RecyclerView.Adapter class. I like to start by building out the ViewHolder, so let’s dissect that:

	class TaskAdapter(var tasks: MutableList<Task> = ArrayList()) {
	    
	    inner class TaskViewHolder(view: View?) : RecyclerView.ViewHolder(view) {
	        val descriptionTextView = view?.findViewById(R.id.task_description) as? TextView
	        val completedCheckBox = view?.findViewById(R.id.task_completed) as? CheckBox

	        fun bindTask(task: Task) {
	            descriptionTextView?.text = task.description
	            completedCheckBox?.isChecked = task.completed
	            
	            completedCheckBox?.setOnCheckedChangeListener { buttonView, isChecked -> 
	                tasks[adapterPosition].completed = isChecked
	            }
	        }
	    }
	}

Here we create an inner class that is a ViewHolder, it takes in a View that is passed as a parameter to the constructor of the super class as well. We defined our two UI elements, and wrote a bind method that takes in a task and displays it accordingly. We’ve also added an OnCheckedChangeListener that modifies the task at a given position. Implementing the rest of the adapter is pretty straight forward. Here’s what the final results look like:

	class TaskAdapter(var tasks: MutableList<Task>) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {

	    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): TaskViewHolder {
	        val context = parent?.context
	        val view = LayoutInflater.from(context)?.inflate(R.layout.list_item_task, parent, false)
	        return TaskViewHolder(view)
	    }

	    override fun onBindViewHolder(holder: TaskViewHolder?, position: Int) {
	        holder?.bindTask(tasks[position])
	    }

	    override fun getItemCount(): Int {
	        return tasks.size
	    }

	    inner class TaskViewHolder(view: View?) : RecyclerView.ViewHolder(view) {
	        val descriptionTextView = view?.findViewById(R.id.task_description) as TextView
	        val completedCheckBox = view?.findViewById(R.id.task_completed) as CheckBox

	        fun bindTask(task: Task) {
	            descriptionTextView.text = task.description
	            completedCheckBox.isChecked = task.completed

	            completedCheckBox.setOnCheckedChangeListener { buttonView, isChecked ->
	                tasks[adapterPosition].completed = isChecked
	            }
	        }
	    }
	}

Next we need to modify the content_main.xml file to include a RecyclerView:

	<?xml version="1.0" encoding="utf-8"?>
	<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:layout_width="match_parent"
	    android:layout_height="match_parent"
	    app:layout_behavior="@string/appbar_scrolling_view_behavior"
	    tools:context="com.adammcneilly.todolist.MainActivity"
	    tools:showIn="@layout/activity_main">

	    <android.support.v7.widget.RecyclerView
	        android:id="@+id/task_list"
	        android:layout_width="match_parent"
	        android:layout_height="match_parent" />

	</android.support.constraint.ConstraintLayout>

Now in our MainActivity.kt file we can add the following in onCreate():

	val recyclerView = findViewById(R.id.task_list) as RecyclerView
	val layoutManager = LinearLayoutManager(this)
	val adapter = TaskAdapter(getSampleTasks())
	recyclerView.layoutManager = layoutManager
	recyclerView.adapter = adapter

The getSampleTasks() method is a private method I’ve added just for testing:

	private fun getSampleTasks(): MutableList<Task> {
	    val task1 = Task("task1")
	    val task2 = Task("task2", true)

	    return mutableListOf(task1, task2)
	}

Note: In this context it makes sense to have the adapter defined right before the RecyclerView, but later in the tutorial you’ll want to have it defined at the class level of your activity.

At this point, let’s run our app and verify that our list appears. Following all of the above steps, this is what you can expect to see:

AndroidEssence

Now that you have a working RecyclerView, you can move on to part 3. If you’ve missed any code, you can find it on GitHub.

Adam McNeilly

Adam McNeilly
Computer Engineering graduate from Oakland University and lover of all things Android.

Leveraging The Robot Pattern For Espresso Tests

Demonstrates the benefits of the Robot pattern for automated testing in Android. Continue reading

Understanding Nullability In Kotlin

Published on June 28, 2017