RecyclerView Day 2: List of Emails

Build a basic email client with RecyclerView.

In the past tutorial in this series, we created a list of Friends. In this tutorial, we'll use Kotlin, Android Studio and Kotlin to build a basic email client that will display a list of emails. Think of Gmail, Outlook or your favorite email app.

After creating a new Android Studio project (make sure Kotlin is checked in the setup dialog), open MainActivity.kt and you should see the following.

package com.example.recyclerviewemailclient

import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity;
import android.view.Menu
import android.view.MenuItem

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

We don't need all of this code. Delete everything but the following.

package com.example.recyclerviewemailclient

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }
}
Tip: Use the keyboard shortcut to format your code.

Open content_main.xml in Text mode (see bottom tab in Android Studio) and you should see the following.

<?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=".MainActivity"
    tools:showIn="@layout/activity_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Replace it with a RecyclerView. You can either drag in a RecyclerView widget from the Design tab, or use the Text mode and paste in the following.

<?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=".MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

Notice that content_main.xml has a RecyclerView, but it's layout_width and layout_height are both set to wrap_content. Let's change that. Set both to 0dp. But we aren't finished yet because this is ConstraintLayout, and we need to constrain the left, top, right, and bottom.

<?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=".MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Last, give this RecyclerView an id of emailRecyclerView.


    <android.support.v7.widget.RecyclerView
        android:id="@+id/emailRecyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Great! We finished our work building content_main.xml. Now let's move on to the setup we have to do in MainActivity.kt. At the bottom of the onCreate method, add this:

        emailRecyclerView.apply {
            
        }

This works because emailRecyclerView is an id inside our content_main.xml file. I have covered apply already here on Android Everywhere. It's a nice convenience feature provided by Kotlin. Let's finish the apply by adding the following, which will give us an error (for now).

        emailRecyclerView.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = EmailAdapter(emails)
        }

First, we need to create our model; a data class, to be specific. Create a new file and name it Email. Then paste in the following.

data class Email(
    val subject: String,
    val text: String
)

To keep things simple, we will only include a subject and text for each email. In real life, you would probably have a received_date, and a field for file attachments.

Now we can actually use the Email data class we created. Open MainActivity.kt and paste the following into the onCreate method.


        val emails = mutableListOf<Email>()
        for (i in 1..100) {
            emails.add(
                Email(
                    "Schedule a meeting today?",
                    "Hi Joe, I'd like to schedule an email for today. Are you available at 4pm?"
                )
            )
        }

This will create a new variable emails, which is a changeable (mutable) list of Email's. In this case, 100 emails.

Did you notice the error on EmailAdapter? We haven't yet created the EmailAdapter class, which is our RecyclerView. Let's do that now. Create a new Kotlin class (file) called EmailAdapter.

class EmailAdapter {
}

This needs to extend RecyclerView.Adapter, and take a RecyclerView.ViewHolder.

class EmailAdapter : RecyclerView.Adapter<EmailAdapter.ViewHolder>() {
}

This gives us an error, but it's a good start. Click on ViewHolder and press Alt+Enter to create the class ViewHolder (This can be saved EmailAdapter, not a separate file.) After creating the ViewHolder class, your code should look like this:

class EmailAdapter : RecyclerView.Adapter<EmailAdapter.ViewHolder>() {
    class ViewHolder {

    }
}

Change the ViewHolder class to take a view of type View, and extend RecyclerView.ViewHolder, passing in the view.

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    }

Let's continue working on EmailAdapter. Did you notice the error in EmailAdapter? Click on EmailAdapter then press Alt+Enter to implement the methods.

class EmailAdapter : RecyclerView.Adapter<EmailAdapter.ViewHolder>() {
    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ViewHolder {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun getItemCount(): Int {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun onBindViewHolder(p0: ViewHolder, p1: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    }
}

Next, we need to accept the list of emails that is being sent to this class from MainActivity.

class EmailAdapter(val emails: List<Email>) : RecyclerView.Adapter<EmailAdapter.ViewHolder>() {

Now we can use this emails variable to finish our first method, getItemCount.

    override fun getItemCount() = emails.size

Next, let's work on onCreateViewHolder, the method that provides a view and sets up the layout (design/UI) for each email.

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

This is giving us an error because the file email_row has not yet been created. Create a new layout file called email_row.

Tip: As is common in Android Studio, click on the error, then press Alt+Enter to view a selection of "quick fixes."

The layout file email_row should contain the following.

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Testing Android RecyclerView!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Now run the app!

Tip: Seeing a blank screen or lots of white space in the Emulator? It's important to set the parent's layout_height to wrap_content. With this option, each row (email) will only take up enough room for the email, and nothing more.

We need to actually display the correct email. Let's do that now. First, give an id of subject to the TextView in our email_row.xml file.

    <TextView
        android:id="@+id/subject"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Testing Android RecyclerView!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

After we give our TextView an id, we can use that id in our EmailAdapter class. Specifically our ViewModel:

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val subject = view.subject!!
    }

We can now use this subject variable and set it's text to the correct position (1 through 100) of email.'

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.subject.text = emails[position].subject
    }

Run the app and you should see the subject we gave to our emails variable. That's basically it!

More Tutorials

Build a Ridesharing Android App - Part 1

Getting started is sometimes the hardest part.

setOnClickListener

Add a click listener in Kotlin.

Android Login Layout

Let users sign in.