Avin's Blog

How to create a RecyclerView with custom Adapter in Kotlin

April 20, 2021
Tags: Android, Android Basics, Github Repository App, RecyclerView,

Components of a RecyclerView

RecyclerView: The view on the screen where the list will be displayed.

Item/data: The list of items that will be displayed.

Adapter: Maps the data to views.

ViewHolders - A pool of views for RecyclerView to use and reuse to display. ViewHolders are created and reused in the Adapter.

Overview

  1. Add required dependency.
  2. Create layout for your list items.
  3. Add RecyclerView to the layout.
  4. Create a ViewHolder.
  5. Create an Adapter.
  6. Create and connect RecyclerView and Adapter in the Activity/Fragment.
  7. Connect or change the data source if needed.

Step 1: Add dependencies

Add the dependency to your app level gradle file.

implementation "androidx.recyclerview:recyclerview:1.1.0"

Step 2: Create layout for the list items

Create a listitem.xml in the layout folder (res -> layout -> listitem.xml) and create a layout. This layout defines how each of the item in the list will look. Make sure to give ids to widgets that will change with the data, for example any TextViews where you might need to change the text or visibility like title, count, etc

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="8dp">

    <TextView
        android:id="@+id/tv_repo_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold"/>

        ...
        ...
</LinearLayout>

Step 3: Add RecyclerView to the layout

Add the recycler view widget to your Activity or Fragment layout wherever you want to display the RecyclerView.

NOTE: I have defined my layout manager and orientation here. You can change the layout manager and orientation here or when you create an instance of your custom adapter in the Activity/Fragment.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_repo_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:orientation="vertical"/>

Step 4: Create a ViewHolder

Create a new Kotlin class for the ViewHolder. Here we will find all the views from our list view and bind data to them. I have create a bind function for that takes in a GithubRepository which is a data class that I created to hold Github repo data and binds data from the object to the views.

// RepositoryViewHolder.kt

class RepositoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
    // Find all the views of the list item
    private val mRepositoryName: TextView = itemView.findViewById(R.id.tv_repo_name)
    private val mRepositoryDescription: TextView = itemView.findViewById(R.id.tv_repo_description)
    private val mRepositoryStarCount: TextView = itemView.findViewById(R.id.tv_star_count)
    private val mRepositoryLanguage: TextView = itemView.findViewById(R.id.tv_language)
    private val mRepositoryUpdatedAt: TextView = itemView.findViewById(R.id.tv_updated_at)
    private val mRepositoryLicense: TextView = itemView.findViewById(R.id.tv_license)

    // Show the data in the views
    fun bind(repo: GithubRepository) {
        val name = repo.name
        val description = repo.description
        val stargazersCount = repo.stargazersCount
        val language = repo.language
        val updatedAt = repo.updatedAt
        val license = repo.license
        mRepositoryName.text = name
        mRepositoryStarCount.text = stargazersCount.toString()
        mRepositoryUpdatedAt.text = updatedAt

        // Since the data in these can be null we check and bind data
        // or remove the view otherwise
        bindOrHideTextView(mRepositoryDescription, description)
        bindOrHideTextView(mRepositoryLanguage, language)
        bindOrHideTextView(mRepositoryLicense, license)
    }

    private fun bindOrHideTextView(textView: TextView, data: String?) {
        if (data == null) {
            textView.visibility = View.GONE
        } else {
            textView.text = data
            textView.visibility = View.VISIBLE
        }
    }
}

Step 5: Create an Adapter

Start building your adapter by creating a new Kotlin class. Implement RecyclerView.Adapter<YourViewHolder>(). It should look something like this

class GithubRepositoryAdapter() : RecyclerView.Adapter<RepositoryViewHolder>() {
}

At this point you would see the red squiggly like under you class name, implement the methods from the prompt. Now you need to complete the three functions:

  1. onCreateViewHolder: This function is called whenever a new viewHolder needs to be created, so you just need to inflate the list item layout you created earlier and pass it on to a new instance of a your ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepositoryViewHolder {
        val context = parent.context
        val inflater = LayoutInflater.from(context)
        val view = inflater.inflate(R.layout.repository_list_item, parent, false)
        return RepositoryViewHolder(view)
    }
  1. onBindViewHolder: is called to attach data to a ViewHolder. Here you change the text, color, whatever needs to be done to the list item views according to the data being displayed. I created a bind function in the ViewHolder earlier for convenience which I use here, if you want you could do everything in the bind function here instead, I find this to be cleaner.
override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) {
        holder.bind(mAllRepositories!![position])
}
  1. getItemCount: Here you return the total number of items in the RecyclerView.
override fun getItemCount(): Int {
        return if (mAllRepositories == null) 0 else mAllRepositories!!.size
}

So your adapter should look something like this. I also created a function which I can call from the Activity or Fragment to change/update the data.

class GithubRepositoryAdapter() : RecyclerView.Adapter<RepositoryViewHolder>() {
    private val TAG = javaClass.simpleName
    private var mAllRepositories: Array<GithubRepository>? = null


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

    override fun onBindViewHolder(holder: RepositoryViewHolder, position: Int) {
        holder.bind(mAllRepositories!![position])
    }

    override fun getItemCount(): Int {
        return if (mAllRepositories == null) 0 else mAllRepositories!!.size
    }

    fun setmAllRepositories(repositories: Array<GithubRepository>?) {
        mAllRepositories = repositories
        notifyDataSetChanged()
    }
}

Step 6: Create and connect RecyclerView and Adapter in the Activity/Fragment

Create variables for the RecyclerView and the Adapter in your Activity/Fragment

class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
    ...
    ...

    private lateinit var mRepositoryRecyclerView: RecyclerView
    private lateinit var mGithubRepositoryAdapter: GithubRepositoryAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        ...
        mRepositoryRecyclerView = findViewById(R.id.rv_repo_list)
        mGithubRepositoryAdapter = GithubRepositoryAdapter()
        mRepositoryRecyclerView.adapter = mGithubRepositoryAdapter
        ...
        ...
    }

    ...
    ...
}

Step 7: Connect or change the data source if needed.

If you have static data a good option is to pass that data as parameter to the Adapter. I am using a ViewModel here with LiveData to observe changes in the list of GitHubRepositories and then send the changes to the adapter.

viewModel.repos.observe(this, { githubRepositories ->
    mRepos = githubRepositories
    mGithubRepositoryAdapter.setmAllRepositories(githubRepositories)
    mRepositoryRecyclerView.visibility = View.VISIBLE
    mProgressBar.visibility = View.GONE
})

Hopefully you feel more comfortable with using RecyclerView. If you find any errors, have feedback or just want to say hi please don’t hesitate to leave a comment below.