DEV Community

Cover image for Kotlin and retrofit network calls
Odhiambo Paul
Odhiambo Paul

Posted on • Updated on

Retrofit Kotlin Kotlin and retrofit network calls

Kotlin and retrofit network call tutorial

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.

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

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

    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'
Enter fullscreen mode Exit fullscreen mode


`

TMDB Api

We are going to use a service called TMDB which provides details about
movie shows. This will provide data to our app.

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

Go ahead and create a project in android studio with an empty activity as the
first activity.

Select the language to Kotlin.

You can give your app any name, in my case I named it Movies.

activity_main.xml

In the activity_main.xml, add some title and RecyclerView to display
our data.

`

<?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> 
Enter fullscreen mode Exit fullscreen mode


`

Data Class

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.

`

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
)
Enter fullscreen mode Exit fullscreen mode


`

The interface

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.

`

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>

}
Enter fullscreen mode Exit fullscreen mode


`

The Service

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.

`

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)
    }
}
Enter fullscreen mode Exit fullscreen mode


`

Making the call

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

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

`

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()
            }
        })
    }
}

Enter fullscreen mode Exit fullscreen mode


`

RecyclerView adapter

`

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()
    }

}

Enter fullscreen mode Exit fullscreen mode


`
Recyclerview item layout.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>
Enter fullscreen mode Exit fullscreen mode


`

String resources

`

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

Enter fullscreen mode Exit fullscreen mode


`

Top comments (10)

Collapse
 
davidmartinezfl profile image
David Martinez

In this post it was missing adding the internet permission in the AndroidManifest.xml

"android.permission.INTERNET"

It helped me a lot, thanks!

Collapse
 
turicahoria profile image
TuricaHoria

I have a problem , I cannot get onto onResponse , I only get onFailure. This is my code:

object RetrofitClientInstance {

private val BASE_URL = "https://wger.de/api/v2/"

private val httpClient = OkHttpClient.Builder()

private val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .client(httpClient.build())
            .build()

fun<T> buildService (service : Class <T>) : T {
    return  retrofit.create(service)
}
Enter fullscreen mode Exit fullscreen mode

}

interface APIServices {

@GET("exercise")
fun getAllExercises(): Call<MutableList<Exercise>>
Enter fullscreen mode Exit fullscreen mode

}

class ExerciseFragment : Fragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_exercises, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    val request = RetrofitClientInstance.buildService(APIServices::class.java)

    val call = request.getAllExercises()
    call.enqueue(object : Callback<MutableList<Exercise>>
        {
            override fun onFailure(call: Call<MutableList<Exercise>>, t: Throwable) {
                Toast.makeText(context,"Couldn't fetch the exercises" , Toast.LENGTH_SHORT).show()
            }

            override fun onResponse(call: Call<MutableList<Exercise>>, response: Response<MutableList<Exercise>>) {

                if (response.isSuccessful)
                {
                    rv_exercices.apply {
                        setHasFixedSize(true)
                        layoutManager = LinearLayoutManager(context)
                        adapter = ExercisesAdapter(response.body()!!.toMutableList())
                    }
                }
            }
        }
    )
}
Enter fullscreen mode Exit fullscreen mode

}

Collapse
 
paulodhiambo profile image
Odhiambo Paul

in your code response.body()!! Should return a MutableList
Just pass response.body()!! to your recyclerview adapter instead of response.body()!!.toMutableList()

Collapse
 
petestmart profile image
Pete St. Martin • Edited

Is anyone else having an issue getting the images (the movie posters) to load?

I would post my code, but I'm not sure where this is even coming from. I'm combing over it and trying different things in different places.

Collapse
 
petestmart profile image
Pete St. Martin • Edited

Figured it out!

In the RecyclerView adapter

Glide.with(itemView.context).load("http://image.tmdb.org/t/p/w500${movie.poster_path}").into(photo)

Should be:

Glide.with(itemView.context).load("https://image.tmdb.org/t/p/w500${movie.poster_path}").into(photo)

I don't know if things have changed, but the http should be an https or nothing will send.

Collapse
 
thihaaungeng profile image
Thiha Aung

Do you have any other tutorials about items clicking and displaying detail pages?

Collapse
 
paulodhiambo profile image
Odhiambo Paul • Edited

I have one that I will be publishing within the next few days.

Collapse
 
microsoftjulius profile image
microsoftjulius

I dont know why when i run the code it returns the error E/RecyclerView: No adapter attached; skipping layout

Collapse
 
paulimjr profile image
Paulo Cesar

This is because you forget to register your adapter in your RecycleView

Basically you can do something like that. example below:

val listOfItems = lisfOf("1", "2", "3")
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = YourAdapter(listOfItems)

This is quick example that you following. I hope helped you. see u.

Collapse
 
paulodhiambo profile image
Odhiambo Paul

Please share your code through gist so that I can help you out