Kotlin

RecyclerView Kotlin Tutorial

In this android app tutorial, I’ll explain, RecyclerView Kotlin implementation in a step by step. In this sample application, we’ll use Kotlin programming language along with RxAndroid and Dagger2. So let’s get started.

What is RecyclerView

If you are an android developer you already familiar RecyclerView. When you need to show scrolling list based on large data set, you should use RecyclerView.

RecyclerView is a flexible and upgraded version of ListView. In the RecyclerView several different works together to display your data. Basically, RecyclerView works on ViewHolder design pattern. Each row managed by ViewHolder. So you can say, each view holder is in responsible for displaying a single item with a view.

Technology Used

In this RecyclerView Kotlin tutorial, I’ll create a sample application that fetch the users details from server and will displayed on RecyclerView. In this sample application, We’ll use following technology

  • Androidx Support Lib
  • Retrofit for APIs calling
  • RxJava
  • Glide
1. Create a RecyclerView Sample App

Let’s move to Android Studio, create a new project with androidx and choose Kotlin language from language dropdown.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.recyclerviewexample"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

def lifeCycleExtensionsVersion = '1.1.1'
def retrofitVersion = '2.3.0'
def daggerVersion = '2.13'
def glideVersion = '4.9.0'
def rxJavaVersion = '2.1.1'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"

    implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
    implementation "io.reactivex.rxjava2:rxandroid:$rxJavaVersion"

    implementation "com.google.dagger:dagger:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    kapt "com.google.dagger:dagger-compiler:$daggerVersion"
    kapt "com.google.dagger:dagger-android-processor:$daggerVersion"

    implementation "android.arch.lifecycle:extensions:$lifeCycleExtensionsVersion"

    implementation "com.github.bumptech.glide:glide:$glideVersion"


    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
}

After adding these dependencies, Sync the project.

2. Open the MainActvitiy layout file and paste below code.

In this layout file, I’m adding the following widget.

  • RecyclerView for showing user list.
  • AppCompatTextView for showing an error message.
  • ProgressBar for showing loader while getting data from a remote server.
  • SwipeRefreshLayout as a root view for refreshing list item on pull to refresh.
 <?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/swipeRefreshLayout"
        tools:context=".view.MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/usersList"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>

        <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/list_error"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:text="@string/error_text"
                android:gravity="center"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>

        <ProgressBar
                android:id="@+id/loading_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
2. Create a data model class for below JSON

The JSON payload looks like below. I need to write a data model to parse that JSON.

{
  "page": 1,
  "per_page": 6,
  "total": 12,
  "total_pages": 2,
  "data": [
    {
      "id": 1,
      "email": "george.bluth@reqres.in",
      "first_name": "George",
      "last_name": "Bluth",
      "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
    },
    .
    . 
  ]
}
2.1 For parsing above JSON I’m creating below data model class named is Data.kt
package com.recyclerviewexample.model

import com.google.gson.annotations.SerializedName

data class Data(
    @SerializedName("page")
    val page: Int,
    @SerializedName("per_page")
    val perPage: Int,
    @SerializedName("total")
    val total: Int,
    @SerializedName("total_pages")
    val totalPages: Int,
    @SerializedName("data")
    val users: List<User>
)
2.2 Create another data model class named is User.kt
package com.recyclerviewexample.model

import com.google.gson.annotations.SerializedName

data class User(
    @SerializedName("avatar")
    val avatar: String,
    @SerializedName("email")
    val email: String,
    @SerializedName("first_name")
    val firstName: String,
    @SerializedName("id")
    val id: Int,
    @SerializedName("last_name")
    val lastName: String
)
3. Prepare an XML layout for showing list user list item.

For displaying the user on a list item in RecyclerView, creates layout named is item_user.xml. It contains ImageView for use image and TextViews for the display name and email.

<?xml version="1.0" encoding="utf-8"?>
<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="@dimen/layout_height"
        android:layout_margin="4dp">

    <ImageView
            android:id="@+id/imageView"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="@dimen/standard_padding" />

    <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:gravity="center_vertical"
            android:orientation="vertical">

        <TextView
                android:id="@+id/name"
                style="@style/Title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Name" />

        <TextView
                android:id="@+id/email"
                style="@style/Text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Email" />

    </LinearLayout>

</LinearLayout>
4. Create a RecyclerView Adapter that holds that holds user item.

For the above layout creates a RecycleView holder adapter that holds the user list item

package com.recyclerviewexample.view

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.recyclerviewexample.R
import com.recyclerviewexample.model.User
import com.recyclerviewexample.util.loadImage
import kotlinx.android.synthetic.main.item_user.view.*


class UserListAdapter(var users: ArrayList<User>) :
    RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {

    fun updateUsers(newUsers: List<User>) {
        users.clear()
        users.addAll(newUsers)
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, p1: Int) = UserViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
    )

    override fun getItemCount() = users.size

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(users[position])
    }

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

        private val imageView = view.imageView
        private val userName = view.name
        private val userEmail = view.email

        fun bind(country: User) {
            userName.text = country.firstName + " " + country.lastName
            userEmail.text = country.email
            imageView.loadImage(country.avatar)
        }
    }
}
5. Create a Util class for with help us to display user profile picture
package com.recyclerviewexample.util

import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.recyclerviewexample.R

fun ImageView.loadImage(uri: String?) {
    val options = RequestOptions()
        .placeholder(R.drawable.loader)
        .circleCrop()
        .error(R.mipmap.ic_launcher_round)
    Glide.with(this.context)
        .setDefaultRequestOptions(options)
        .load(uri)
        .into(this)
}
6. Step up the Retrofit client for calling APIs

In src folder create a new Retrofit calling interface named is UsersApi

package com.recyclerviewexample.model

import io.reactivex.Single
import retrofit2.http.GET

interface UsersApi {

    @GET("users")
    fun getUsers(): Single<Data>
}
7. Now create Dagger module file name is ApiModule

In this dagger module, we are providing Retrofit client and User Service using @provide annotation

package com.recyclerviewexample.di

import com.recyclerviewexample.model.UsersApi
import com.recyclerviewexample.model.UsersService
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory

@Module
class ApiModule {

    private val BASE_URL = "https://reqres.in/api/"

    @Provides
    fun provideUsersApi(): UsersApi {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(UsersApi::class.java)
    }

    @Provides
    fun provideUsersService(): UsersService {
        return UsersService()
    }
}
8. Let prepare to dagger component named is ApiComponent

In this ApiComponent interface, we are injecting User Service and ListView Model that I’ll create later.

package com.recyclerviewexample.di

import com.recyclerviewexample.model.UsersService
import com.recyclerviewexample.viewmodel.ListViewModel
import dagger.Component

@Component(modules = [ApiModule::class])
interface ApiComponent {

    fun inject(service: UsersService)

    fun inject(viewModel: ListViewModel)
}
9. Now I’m creating a service provider class for calling named is UsersService.
package com.recyclerviewexample.model


import com.recyclerviewexample.di.DaggerApiComponent
import io.reactivex.Single
import javax.inject.Inject

class UsersService {

    @Inject
    lateinit var api: UsersApi

    init {
        DaggerApiComponent.create().inject(this)
    }

    fun getUsers(): Single<Data> {
        return api.getUsers()
    }
}
10. Finally, create a subclass of ViewModel name is ListViewModel

Create a class named is ListViewModel, which extends the ViewModel. In this class, we using fetching user list from a remote server and updating ViewModel value.

package com.recyclerviewexample.viewmodel

import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.recyclerviewexample.di.DaggerApiComponent
import com.recyclerviewexample.model.Data
import com.recyclerviewexample.model.User
import com.recyclerviewexample.model.UsersService
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject

class ListViewModel : ViewModel() {

    @Inject
    lateinit var usersService: UsersService

    init {
        DaggerApiComponent.create().inject(this)
    }

    private val disposable = CompositeDisposable()

    val users = MutableLiveData<List<User>>()
    val userLoadError = MutableLiveData<Boolean>()
    val loading = MutableLiveData&l