Kotlin and retrofit network calls
Tags: kotlin, retrofit, gson, glide
Published: 2020-03-01 04:06:35

**Kotlin and retrofit network call tutorial**<br>

**What is retrofit**
This is a rest client library for java and android. This means that we 
use retrofit to make network calls from our applications.<br>
In android applications, Retrofit makes use of OkHttp which is a low level
Http client fro android created by the same company that created retrofit.
[Square.io](square.io)<br>

We are also going to use Gson. This is a library used to convert java objects
into a JSON format. But in our case, we will be converting JSON to java object.
We will be getting data from the network in a JSON format and through GSON converter
we will convert it to an object we can use in our application.

**Dependencies**
````groovy   
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'  
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
    implementation 'com.squareup.okhttp3:okhttp:4.3.1'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'com.github.bumptech.glide:glide:4.10.0' //Glide
    annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
````
    
**TMDB Api**<br>
We are going to use a service called [TMDB](https://www.themoviedb.org) which provides details about
movie shows. This will provide data to our app.<br>
In order to use the service you need an account there and an api key. This is a
key that allows you to access their service. Go ahead and follow the link
above and follow their instructions on how you can get your api key.


**Project Setup**<br>
Go ahead and create a project in android studio with an empty activity as the
first activity.<br>
Select the language to Kotlin.<br>
You can give your app any name, in my case I named it Movies.

**activity_main.xml**<br>
In the activity_main.xml, add some title and RecyclerView to display
our data.


````xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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">
    <TextView
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Popular Movies"
        android:layout_margin="20dp"
        android:id="@+id/title"
        android:textSize="20sp"/>

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_below="@+id/title"
        android:id="@+id/recyclerView"/>

    <ProgressBar
        android:id="@+id/progress_bar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:scaleY="4"
        android:indeterminateTint="@color/colorPrimary"
        android:indeterminateBehavior="repeat"
        android:indeterminate="true" />
</RelativeLayout> 
````


**Data Class**<br>
Now we have to create a data class to define how our data from the network
will look like and hold it temporarily. Let's create a kotlin file and name 
it TmdbData.

````kotlin
package com.odhiambopaul.movies

data class PopularMovies(
    val results: List<Result>
)

data class Result(
    val id: Int,    val overview: String,
    val poster_path: String,
    val release_date: String,
    val title: String,
    val vote_average: Double,
    val vote_count: Int
)
````

**The interface**<br>
Now we are going to create an interface to define the various endpoints
of the TMDB api. This interface will contain methods which when called 
will run the api using the specified endpoints. Just create an interface and name it
TmdbEndpoints, Then add the code as show below. The @GET tells retrofit that
this call is of the type GET, that gets data back as a response from the server.
Inside the brackets of it is the endpoint we will use to make the call.
This is the path that is defined in the TMDB api for getting popular movies.
After that we create a function called *getmovies* that we will return a call
object containing data which is in the form of *PopulaMovies* object of the PopulaMovies 
data class we created.Here is where we pass our api_key as query parameter. We
use the @Query annotation. Inside the parenthesis, we pass in the name of the query
then pass in the value after the query annotation. When we call this method
we will then pass in the parameter at that point.

````kotlin
package com.odhiambopaul.movies

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface TmdbEndpoints {

    @GET("/3/movie/popular")
    fun getMovies(@Query("api_key") key: String): Call<PopularMovies>

}
````

**The Service**<br>
Next, we create a kotlin file and name it *ServiceBuilder* and an object
with the same name. This will be used to prepare retrofit to make the call.
First we make an OKhttp client for retrofit to use to make a call.
Next we create a retrofit builder object that will contain the base url of the api,
the converter *Gson Converter* and the client. Then we create a function called
buildService that will be used to connect the retrofit builder object with our
interface and make one complete retrofit call. The interface will be passed in as 
a parameter in this method. This method will return a full retrofit object on which 
we can call the various method *that we define in the interface*.

````kotlin
package com.odhiambopaul.movies

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ServiceBuilder {
    private val client = OkHttpClient.Builder().build()

    private val retrofit = Retrofit.Builder()
        .baseUrl("https://api.themoviedb.org/")
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build()

    fun<T> buildService(service: Class<T>): T{
        return retrofit.create(service)
    }
}
````

**Making the call**<br>
First we make a variable called request that will be holding our whole
retrofit object. We get the *ServiceBuilder* object, call the buildService
function and pass in our interface. Then we create a call by using the retrofit 
object and calling one of the methods in the interface which our case is *getMovies*.
Then we pass in the required api_key as we defined in our interface function.
After that we send the request to the network using the Call.enque method of retrofit.
Inside it we pass a callback that gives us a response in the form of *PopularMovies* object. Then 
we implement the two methods: *onResponse* and *onFailure*.

**onResponse**<br>
In the on Response, the call has returned a response and there you can get
data if the request was successful. Then you can add the response data into the
recyclerview . You only need to pass in the list of Movies to the recyclerview adapter
and the populate the data accordingly

````kotlin
package com.odhiambopaul.movies

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

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

        val request = ServiceBuilder.buildService(TmdbEndpoints::class.java)
        val call = request.getMovies(getString(R.string.api_key))

        call.enqueue(object : Callback<PopularMovies>{
            override fun onResponse(call: Call<PopularMovies>, response: Response<PopularMovies>) {
                if (response.isSuccessful){
                    progress_bar.visibility = View.GONE
                    recyclerView.apply {
                        setHasFixedSize(true)
                        layoutManager = LinearLayoutManager(this@MainActivity)
                        adapter = MoviesAdapter(response.body()!!.results)
                    }
                }
            }
            override fun onFailure(call: Call<PopularMovies>, t: Throwable) {
                Toast.makeText(this@MainActivity, "${t.message}", Toast.LENGTH_SHORT).show()
            }
        })
    }
}

````

**RecyclerView adapter**<br>

````kotlin
package com.odhiambopaul.movies

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

class MoviesAdapter(val movies: List<Result>): RecyclerView.Adapter<MoviesViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.movie_item, parent, false)
        return MoviesViewHolder(view)
    }

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

    override fun onBindViewHolder(holder: MoviesViewHolder, position: Int) {
        return holder.bind(movies[position])
    }
}

class MoviesViewHolder(itemView : View): RecyclerView.ViewHolder(itemView){
    private val photo:ImageView = itemView.findViewById(R.id.movie_photo)
    private val title:TextView = itemView.findViewById(R.id.movie_title)
    private val overview:TextView = itemView.findViewById(R.id.movie_overview)
    private val rating:TextView = itemView.findViewById(R.id.movie_rating)

    fun bind(movie: Result) {
        Glide.with(itemView.context).load("http://image.tmdb.org/t/p/w500${movie.poster_path}").into(photo)
        title.text = "Title: "+movie.title
        overview.text = movie.overview
        rating.text = "Rating : "+movie.vote_average.toString()
    }

}

````
**Recyclerview item layout.xml**

````xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:layout_centerHorizontal="true"
                android:id="@+id/movie_photo"
                android:contentDescription="Movie Image" />

            <TextView
                android:layout_below="@+id/movie_photo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/movie_title"
                android:layout_margin="5dp"
                android:textSize="15sp"
                android:text="Title : "/>

            <TextView
                android:layout_below="@+id/movie_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/movie_overview"
                android:layout_margin="5dp"
                android:text="OverView : "/>

            <TextView
                android:layout_below="@+id/movie_overview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/movie_rating"
                android:textSize="15sp"
                android:layout_margin="5dp"
                android:text="Rating : "/>


        </RelativeLayout>

    </androidx.cardview.widget.CardView>

</RelativeLayout>
````

**String resources**<br>

````xml
<resources>
    <string name="app_name">Movies</string>
    <string name="api_key">YOUR API KEY HERE</string>
</resources>

````