Retrofit Library tutorial with RecyclerView in Kotlin

Hey Technoz, hope you are having a fun time coding. Well, in this tutorial, we are going to see how to use Retrofit Library to make http requests to server and get the data in our app. We will use RecyclerView to display the list data which we fetch from server with Retrofit. In order to make this api call in a background, we are using Kotlin Coroutines which is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously.

Let’s have a look at what type of app we are going to build.


 


We are using some open source json data, parsing it with Retrofit library and showing it in Recyclerview. So, without wasting time, lets get started.

What is Retrofit Library?

Retrofit library is a type-safe REST client library which eases our work of making network calls to apis. Also, it supports the Json data parsing and hence one of the most popular library and widely used. A company named Square, Inc. developed it.

Add required dependencies

Lets add the required dependencies of retrofit library and coroutines lifecycle scope in your module-level build.gradle as follows:

//Retrofit 
implementation 'com.squareup.retrofit2:retrofit:2.9.0' 
implementation 'com.squareup.retrofit2:converter-gson:2.7.0' 
// Coroutine Lifecycle Scopes 
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" 
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"

So, after setting up the dependencies, we are good to go further. Don’t forget to add the Internet permission in AndroidManifest.xml as follows.

<uses-permission android:name="android.permission.INTERNET"/>

Build the layout

Now we are going to build a layout. As we will be showing list of users in MainActivity, we need to add a RecyclerView widget in activity_main.xml as follows.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/users_list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="8dp"
        tools:listitem="@layout/user_layout"/>

    <ProgressBar
        android:id="@+id/progress_bar"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="@+id/users_list_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/users_list_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

In the above code, along with the RecyclerView, we have also added a ProgressBar which we will show till the time api fetch the data from server. In the RecyclerView, we have added tools:listitem="@layout/user_layout" this line which shows the how the layout will be shown in the preview pane. This is fully optional, but for this retrofit Library tutorial, we are doing it as it helps us to see the design preview. So, lets create a layout item file for individual list item (user_layout.xml).

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Leanne Graham"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:text="Sincere@april.biz"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@+id/name"
        app:layout_constraintTop_toBottomOf="@+id/name" />
    <TextView
        android:id="@+id/phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:text="1-770-736-8031 x56442"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@+id/email"
        app:layout_constraintTop_toBottomOf="@+id/email"/>
    <TextView
        android:id="@+id/website"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:text="hildegard.org"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="@+id/phone"
        app:layout_constraintTop_toBottomOf="@+id/phone"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        app:layout_constraintTop_toBottomOf="@+id/website"
        android:background="@color/black"
        android:layout_margin="5dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

So far we what we have done is the layout part. At this part if you run your app, then you won’t see anything. But dude, trust me Hard work pays off! 😜

Set up json data class

The data we are fetching from one online free service. We get a json sample data by simply hitting the url. For this project, we will be pointing to https://jsonplaceholder.typicode.com/users this which returns the sample list of users and we’ll show that in our app. For that, we first need to create a data class as per the json response fields. Now, create a kotlin data class User.kt as follows.

package net.softglobe.retrofittutorial

data class User(
    val email: String,
    val id: Int,
    val name: String,
    val phone: String,
    val username: String,
    val website: String
)
Quick tip:

There is a plugin for Android Studio named “JSON To Kotlin Class” which can generate the kotlin data class by just reading the json response. To install the plugin, go to File-> Settings-> Plugins an search “JSON To Kotlin Class”. Install it. Now you can just right click on package name -> new and you will get an option “Kotlin data class File from JSON”. You will get a dialog where you can paste the json response. Paste the response there and proceed further. The plugin will automatically create a data class with relevant fields. Well, for this retrofit library tutorial, we are using only 6 out of multiple fields in the response, so you can delete other fields.

Create the Adapter

As we are using the RecyclerView to show list items, we need a custom adapter. Create UserAdapter.kt as follows.

package net.softglobe.retrofittutorial

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView

class UserAdapter(private val listener : (User) -> Unit) : ListAdapter<User, UserAdapter.ViewHolder>(DiffUserCallBack()){

    inner class ViewHolder(private val containerView : View) : RecyclerView.ViewHolder(containerView){
        init {
            itemView.setOnClickListener {
                listener.invoke(getItem(adapterPosition))
            }
        }

        fun bind(user : User){
            containerView.findViewById<TextView>(R.id.name).text = user.name
            containerView.findViewById<TextView>(R.id.email).text = user.email
            containerView.findViewById<TextView>(R.id.phone).text = user.phone
            containerView.findViewById<TextView>(R.id.website).text = user.website
        }
    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

class DiffUserCallBack : DiffUtil.ItemCallback<User>(){
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }

}

The above recyclerview is an optimized version which changes the view contents efficiently with the help of ViewHolder. Have a look at the following explanation:

  • The class UserAdapter extends ListAdapter of type User and a ViewHolder and parameter as DiffUserCallBack().
  • Then we create an inner class ViewHolder to hold the view which is being shown and the code in the constructor invokes the current item by specifying the adapterPosition.
  • The class UserAdapter then implements two overridden methods:
    • onCreateViewHolder : It inflates the custom item layout we created earlier and return the ViewHolder object by passing it to the ViewHolder inner class.
    • onBindViewHolder: It explicitly calls the bind() function in the ViewHolder inner class.
  • bind() function takes care of binding the appropriate values to the respective view objects. For ex. in this case, we are assigning the user’s name, email, phone and website to the TextViews.
  • Then we create a class DiffUserCallBack which extends the abstract class DiffUtil and calls ItemCallback<T> static java method. We have to implement their two methods:
    • areItemsTheSame: Here we check if the items are same by comparing the unique fields (in this case, ‘id’) and returns true if they matches, false otherwise.
    • areContentsTheSame : Here we check the actual contents of the two objects and returns true if they are same.

Set up classes for Retrofit Library

Hooossshhh!!! Finally coming to the main part of this tutorial, setting up the things needed for Retrofit Library. The work mostly happens in terms of annotations. There are several type of requests like GET, POST, PUT, DELETE, etc. Here for this Retrofit Library Tutorial, we are using GET request. For that, we will create an api interface in which we will define the requests. Create interface ApiInterface.kt as follows:

package net.softglobe.retrofittutorial

import retrofit2.Response
import retrofit2.http.GET

interface ApiInterface {

    @GET("/users")
    suspend fun getUserData() : Response<List<User>>
}

We have created one function getUserData() to get the user data from api and returns the Response object from retrofit which further contains <List<User>. Above the function, we are specifying the @GET annotation which signifies that this function will be making a GET request to server. For that, we need the api endpoint which will return the expected json data. In this case, it is /users which is defined inside @GET annotation.

Furthermore, we need to create an instance of retrofit library which we can use to make api calls. But, instead of creating it separately, we are creating a singleton instance of retrofit library which we can use anywhere in the app. For that, create an singleton object RetrofitInstance.kt as follows:

package net.softglobe.retrofittutorial

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitInstance {

    val api : ApiInterface by lazy {
        Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiInterface::class.java)
    }
}

In the above file, we are building retrofit library instance using Retrofit.Builder().

  • baseUrl() : defines the base url where the api is located. It does not include endpoint and every request will be sent to this base url with the endpoints further.
  • addConverterFactory() : This method sets the converter method for serialization and deserialization of objects and we pass GsonConverterFactory.create() which creates an instance using Gson for conversion.
  • build() : It creates the Retrofit instance using the configured values.
  • create() : It creates an implementation of the API endpoints defined by the interface passed to it.

Finally, Configure MainActivity

Now coming to the last file in this retrofit library tutorial, which contains the driver code that performs the task and binds everything together. Lets open MainActivity.kt and put the following code in it.

package net.softglobe.retrofittutorial

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val loading = findViewById<ProgressBar>(R.id.progress_bar)
        lifecycleScope.launchWhenCreated {
            loading.visibility = View.VISIBLE
            val response = try{
                RetrofitInstance.api.getUserData()
            }catch (e: Exception){
                Log.e(TAG, "Exception: $e")
                loading.visibility = View.GONE
                return@launchWhenCreated
            }
            if (response.isSuccessful && response.body() != null){
                Toast.makeText(this@MainActivity, ""+response.body()?.size+" users loaded!", Toast.LENGTH_SHORT).show()
                val usersRecyclerView = findViewById<RecyclerView>(R.id.users_list_view).apply {
                    adapter = UserAdapter(){it}
                    layoutManager = LinearLayoutManager(this@MainActivity)
                    setHasFixedSize(true)
                }
                (usersRecyclerView.adapter as UserAdapter).submitList(response.body())
                loading.visibility = View.GONE
            } else {
                Toast.makeText(this@MainActivity, "Something went wrong!", Toast.LENGTH_SHORT).show()
                loading.visibility = View.GONE
            }
        }
    }
}

In above file, the code inside lifecycleScope.launchWhenCreated block will be run code inside on a background thread and we are making a call to the RetrofitInstance.api.getUserData() under the try…catch block and assigning the result to “response” variable.

Furthermore, if the request is successful and returns non-null list, then we assigns the UserAdapter adapter to the “adapter” parameter of RecyclerView and then submits the list which we got from the api response using submitList(response.body()). We start displaying the ProgressBar at the start of request and hides it once the request is finished or in case some exception has occurred.

That’s it about the tutorial! your app is ready to do its work. Now try opening the app and see your results. Congratulations! you learnt how to use Retrofit Library to fetch a data from api.

Download Source code

If you want to skip going through the tutorial, then just go ahead and download the source code by visiting following link:

Source Code Link

If you have any questions or facing some issues implementing above tutorial, feel free to let me know in the comments section below. I will try my best to get back to you. Meet you in the next tutorial, till then bye and Happy Coding!

Good Night! Wondering why I said Good Night? because most of us are owls who are busy with our codes mostly late night. 😂 Including Me, writing this post late night…😁

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.