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'
`
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>
`
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
)
`
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>
}
`
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)
}
}
`
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()
}
})
}
}
`
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()
}
}
`
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>
`
String resources
`
<resources>
<string name="app_name">Movies</string>
<string name="api_key">YOUR API KEY HERE</string>
</resources>
`
Top comments (10)
In this post it was missing adding the internet permission in the AndroidManifest.xml
"android.permission.INTERNET"
It helped me a lot, thanks!
I have a problem , I cannot get onto onResponse , I only get onFailure. This is my code:
object RetrofitClientInstance {
}
interface APIServices {
}
class ExerciseFragment : Fragment() {
}
in your code response.body()!! Should return a MutableList
Just pass response.body()!! to your recyclerview adapter instead of response.body()!!.toMutableList()
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.
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 anhttps
or nothing will send.Do you have any other tutorials about items clicking and displaying detail pages?
I have one that I will be publishing within the next few days.
I dont know why when i run the code it returns the error E/RecyclerView: No adapter attached; skipping layout
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.
Please share your code through gist so that I can help you out