Create a recyclerview with image from API in Kotlin Android

Create a recyclerview with image from API in Kotlin Android:

In our last post, we have learned how to create one basic recyclerview and how to load data in it. In this post, we will move one more step and create one recyclerview with images and text.

I have deployed one API that we can use to load data to the recyclerview.

In short,

  • The app will hit the API and get all data in JSON format.
  • Each element in the JSON includes links for an image, text for title, and text for description to show.
  • We will load the data in each cell of the recyclerview.

How to do that in Android:

If you think it like a developer:

  • Fetch the data using the API
  • Parse the data from the JSON response.
  • Load the data in the recyclerview.

For these, we need the below two libraries:

  • Networking library to fetch the data using the API
  • Parser to parse the JSON data.
  • Library to load the images

For networking, we will use one library called Retrofit and for parsing the JSON response, we will use one parser provided by retrofit called Moshi.

Below is the API endpoint to fetch the data that we are loading :

http://simple-node-app-nkd.herokuapp.com/

If you open it in a browser, you will see the response. It is an array of objects with each object containing id, title, description, and image keys. image key holds the url for the image to load.

Android Project structure:

For this project, We need :

  • One xml view to show each list item in the recyclerview and xml file for the main activity.
  • One network class to make a GET request to the API.
  • One model class that will define the structure of the items we will receive from the API.
  • One Adapter class to load the data in the recyclerview
  • Main Activity class.

Let’s create them one by one.

Android Project:

We have one MainActivity as the parent activity and activity_main.xml as its xml file.

Gradle changes:

In your application level build.gradle file, add the below lines:

implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.11.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'

We have added dependencies for recyclerview, cardview, retrofit, moshi and glide. The versions might be different for you.

Internet permission:

We are making API calls, so we need to add permission for internet in the manifest file. Open your AndroidManifest.xml file and add the below line inside manifest tags:

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

Views:

Change the activity_main.xml file as below:

<?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/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="1dp"
        android:layout_marginLeft="1dp"
        android:layout_marginTop="25dp"
        android:layout_marginEnd="1dp"
        android:layout_marginRight="1dp"
        android:layout_marginBottom="1dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

We added one recycler view in a ConstraintLayout.

Similarly, create one list_item.xml and add the below code:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView android:layout_height="wrap_content"
    android:layout_width="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="8dp"
    app:cardElevation="8dp"
    android:layout_margin="5dp">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintWidth_percent=".3"
            app:layout_constraintDimensionRatio="1:1"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            android:src="@drawable/ic_launcher_background"
            />

        <TextView
            android:id="@+id/tvTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toEndOf="@+id/imageView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:textSize="25sp"
            android:fontFamily="sans-serif"
            android:textColor="#212121"
            android:layout_marginStart="10dp"
            android:text="This is a very long title and here it is"
            android:layout_marginLeft="10dp" />
        <TextView
            android:id="@+id/tvDescription"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="@+id/tvTitle"
            app:layout_constraintEnd_toEndOf="@id/tvTitle"
            app:layout_constraintTop_toBottomOf="@+id/tvTitle"
            app:layout_constraintBottom_toBottomOf="parent"
            android:textSize="18sp"
            android:fontFamily="sans-serif"
            android:text="This is a very long title and here it is"
             />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

This xml file is used for each item in the recyclerview. If you open this in the Design tab in Android Studio, it will look as like below :

Android recyclerview item

Model class:

Create one new file Property and add the below code:

data class Property(val id: Int, val title: String, val description: String, val image: String)

This is a data class and it is used to hold the items of the JSON response.

Api service:

Create one class called ApiService.kt with the below code:

import com.example.myapplication.models.Property
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

private const val BASE_URL = "http://simple-node-app-nkd.herokuapp.com"

private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val retrofit = Retrofit.Builder().addConverterFactory(MoshiConverterFactory.create(moshi)).baseUrl(BASE_URL).build()

interface ApiService{

    @GET(".")
    fun getAllData(): Call<List<Property>>

}

object Api {
    val retrofitService: ApiService by lazy{retrofit.create(ApiService::class.java)}
}

We are creating one Retrofit object with moshi as the converter factory retrofit. The ApiService interface contains all API endpoints that we will use.

Adapter class:

Create one class MyAdapter.kt with the below code:

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.myapplication.models.Property
import org.w3c.dom.Text

class MyAdapter(private val data: List<Property>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>()  {

    class MyViewHolder(val view: View): RecyclerView.ViewHolder(view){

        fun bind(property: Property){
            val title = view.findViewById<TextView>(R.id.tvTitle)
            val imageView = view.findViewById<ImageView>(R.id.imageView)
            val description = view.findViewById<TextView>(R.id.tvDescription)

            title.text = property.title
            description.text = property.description

            Glide.with(view.context).load(property.image).centerCrop().into(imageView)
        }
    }

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

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

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


}

This class uses Glide to load the image of Property to the imageview defined in the list_item.xml layout. Here,

  • onCreateViewHolder loads the layout for the list items.
  • getItemCount returns the total number of items to show in the recyclerview
  • bind method takes one Property object and loads its content in the list item.

MainActivity:

Below is the code for our MainActivity :

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.models.Property
import com.example.myapplication.network.Api
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity(){
    private lateinit var recyclerView: RecyclerView
    private lateinit var manager: RecyclerView.LayoutManager
    private lateinit var myAdapter: RecyclerView.Adapter<*>

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

        manager = LinearLayoutManager(this)
        getAllData()
    }

    fun getAllData(){
        Api.retrofitService.getAllData().enqueue(object: Callback<List<Property>>{
            override fun onResponse(
                call: Call<List<Property>>,
                response: Response<List<Property>>
            ) {
                if(response.isSuccessful){
                    recyclerView = findViewById<RecyclerView>(R.id.recycler_view).apply{
                        myAdapter = MyAdapter(response.body()!!)
                        layoutManager = manager
                        adapter = myAdapter
                    }
                }
            }

            override fun onFailure(call: Call<List<Property>>, t: Throwable) {
                t.printStackTrace()
            }
        })
    }
}

Here, we are calling the getAllData method to load all data we received from the API using retrofit. Once it loads all data, onResponse will be called. Inside this method, we are checking if the response is successful or not. If it is successful, we are creating one adapter with the items we received and adding them to the recyclerview.

That’s it, if you run this app now, it will look as like below:

Android recyclerview load image

You might also like: