Here in this article I am demonstrating how you can consume an API using Retrofit in Android with Example. For this specific tutorial we will be using the GET HTTP verb to fetch comic detail from the Xkcd Comics.
Quick Navigation
What is Retrofit ?
According to them Retrofit is a “A type-safe HTTP client for Android and Java“. Now what do they mean by type-safe and HTTP client ? Let me do some break down.
In a simple words type-safe in computer science means that there is no data and data-type mismatch while assigning data to a memory address. It ensures that the data is always correctly referenced. And there are no unexpected errors .
And that brings us to HTTP client. What is a HTTP client ?HTTP client is simply the applications that communicate with HTTP server. Simple enough, yes. Lets dive right into the heart of this article.
Also Read : Learn Android Snackbar the Material Design Way
TECHENUM
Adding the required dependencies
So, now that we have understood what Retrofit really is. We must look at what dependencies we will be using to perform a simple network request using retrofit. The libraries marked with * actually has nothing to do with consuming API.
Things we will be needing:
- Retrofit library
- Gson library
- Lifecycle extensions ( for using view model )
- Coil library ( for image ) *
Let us add the all libraries in our app > build.gradle
file
dependencies {
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// for view model
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
// for image loading
implementation "io.coil-kt:coil:0.13.0"
}
Code language: Groovy (groovy)
Do a gradle sync and we have successfully setup our libraries. One step closer to consume API with Retrofit on Android with example.
Also Read : Learn How to Use Custom Font in Android Application
TECHENUM
Preparing our UI
This step is optional and if you are not following along to every bit of code this is pretty useless. You can skip to the next section. But stay if you are only starting to learn Android.
In my activity_main.xml I have the following code:
<?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">
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
Code language: HTML, XML (xml)
And then I have mapped the views to the MainActivity class as below
class MainActivity : AppCompatActivity() {
private lateinit var mText: TextView
private lateinit var mImageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mText = findViewById(R.id.text)
mImageView = findViewById(R.id.imageView)
}
}
Code language: Kotlin (kotlin)
Now that we have setup our MainActivity let us move forward.
Also Read : Learn how to use shared preferences in Android
TECHENUM
Setting up Retrofit
Now that we have setup our UI we have to set everything else. To complete the Retrofit example for consuming Xkcd API in Android.
We will need the following things
- Service ( the API endpoints )
- Concrete implementation of the service
Now that we have understood this. Let us move forward and see how we prepare each one of them.
Preparing JSON response POKO
First visit this URL, click here. We have to convert the response into the POKO ( Plain Old Kotlin Object ).
data class ComicResponse(
@SerializedName("alt")
var alt: String,
@SerializedName("day")
var day: String,
@SerializedName("img")
var img: String,
@SerializedName("link")
var link: String,
@SerializedName("month")
var month: String,
@SerializedName("news")
var news: String,
@SerializedName("num")
var num: Int,
@SerializedName("safe_title")
var safeTitle: String,
@SerializedName("title")
var title: String,
@SerializedName("transcript")
var transcript: String,
@SerializedName("year")
var year: String
)
Code language: Kotlin (kotlin)
We have the annotation @SerializedName
which maps the JSON key to the variable in the class.
Once we are done mapping JSON to actual variable.
Also Read : Learn How To Create Tabs With ViewPager2
TECHENUM
Creating Xkcd service
Create a Kotlin
interface with the name XkcdApiService
. Yes, this is not a class but an interface.
interface XkcdApiService {
@GET("info.0.json")
fun getFrontPageComic(): Call<ComicResponse>
}
Code language: PHP (php)
For now copy the code above and paste it into your file.
There is a few things we have to understand about the above XkcdApiService interface.
@GET
is an HTTP verb annotation, we will cover about annotations later- The annotation will have some value inside the parentheses
- The return type
Call<>
is a non blocking method which hasonSuccess { }
andonError() { }
. This is where we get our response from server.
Why is an service interface required ?
To learn more about interface you can read: Interface in OOP: Guide to Polymorphism and Callbacks.
Here our library Retrofit takes the interface and created suitable implementation for us to use during the runtime. Meaning it will write the boilerplate code for us.
Also Read : How to get current GPS location in Android
TECHENUM
Preparing Retrofit for use
Now that we have created a POKO and service. It is time for us to create a retrofit implementation which we will be using as a singleton.
let us create an object
named RetrofitClient
as below:
object RetrofitClient {
private lateinit var INSTANCE: XkcdApiService
private const val MS_TIMEOUT = 30L
private const val BASE_URL = "https://xkcd.com/"
fun instance(): XkcdApiService {
if (!::INSTANCE.isInitialized) {
val client = OkHttpClient.Builder().apply {
/*callTimeout(MS_TIMEOUT, TimeUnit.SECONDS)
connectTimeout(MS_TIMEOUT, TimeUnit.SECONDS)
readTimeout(MS_TIMEOUT, TimeUnit.SECONDS)
writeTimeout(MS_TIMEOUT, TimeUnit.SECONDS)*/
}.build()
val retrofit = Retrofit.Builder().apply {
baseUrl(BASE_URL)
client(client)
addConverterFactory(GsonConverterFactory.create())
}.build()
INSTANCE = retrofit.create(XkcdApiService::class.java)
}
return INSTANCE
}
}
Code language: Kotlin (kotlin)
Let me quickly explain what the class does. We have a variable named INSTANCE
where we will be holding the instance of the XkcdApiService
.
We have a OkHttpClient.Builder
which looks after all the network related things such as read / write and call timeout.
Next we have Retrofit.Builder
which actually takes in our URL and GsonFactory
. We have to watch out for 2 main things here.
Base URL : It is nothing but the static part of the API URL which does not change. In our case https://xkcd.com/
. It does not change we will be calling a resource in that domain.
Converter Factory : This portion tells the retrofit how to convert the response to object in Kotlin / Java. We have used Gson
so we will be using GsonConverterFactory
.
We have finally created an instance and assigned it with the following line
INSTANCE = retrofit.create(XkcdApiService::class.java)
Code language: JavaScript (javascript)
Also Read : Async Task in Android is Deprecated: There are Better Ways
TECHENUM
Creating the ViewModel
We are following the MVVM
pattern so let us quickly create a ViewModel
first.
We have to create a ViewModel
named MainActivityViewModel
as below :
class MainActivityViewModel : ViewModel() {
private val mXkcdApiService = RetrofitClient.instance()
fun getComic(): MutableLiveData<ComicResponse?> {
val result = MutableLiveData<ComicResponse?>()
mXkcdApiService.getFrontPageComic().enqueue(object : Callback<ComicResponse> {
override fun onResponse(call: Call<ComicResponse>, response: Response<ComicResponse>) {
if (!response.isSuccessful) {
result.postValue(null)
} else {
result.postValue(response.body())
}
}
override fun onFailure(call: Call<ComicResponse>, t: Throwable) {
result.postValue(null)
}
})
return result
}
}
Code language: HTML, XML (xml)
Remember the method we created earlier in XkcdApiService ?
@GET("info.0.json") fun getFrontPageComic(): Call<ComicResponse>
Code language: HTML, XML (xml)
The Call<T>
has a method named enqueue()
which will perform the network request and passes the result in form of Callback<T>
.
And that is exactly what we have passed in the ViewModel
‘s invocation of enqueue()
. Once the request is complete we will receive the response in either onResponse()
or onFailure()
.
Let me briefly go over what each of the method does.
When the request completes the code inside onResponse()
is executed. The response is complete but we don’t know whether the request succeeded or failed.
So to see if the request succeeded we must invoke response.isSuccessful()
. This will be true if HTTP response code
is between 200
and 299
.
Also Read : ZOOM SDK Android: Learn How to Create an Online Meeting
TECHENUM
Using the ViewModel
Now that we have our ViewModel
ready let us update the MainActivity
code add the code below at the end of onCreate() { }
val vm = ViewModelProvider(this).get(MainActivityViewModel::class.java)
vm.getComic().observe(this, Observer {
if (it == null) {
Log.i("MainActivity", "we received null, there was an error perform inspection.")
} else {
Log.i("MainActivity", "we have alt : ${it.alt}")
mText.text = it.alt
mImageView.load(it.img)
}
})
Code language: Kotlin (kotlin)
We have nothing going on here we have just checked if the value of live data is null. If the value is not null we have updated our TextView
and ImageView
.
Look at the mImageView.load(it.img)
the method .load()
is an extension method provided by the Coil
library which we had used earlier. You could have simply used the Glide
or Picasso
.
The final MainActivity file will look something like this :
class MainActivity : AppCompatActivity() {
private lateinit var mText: TextView
private lateinit var mImageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mText = findViewById(R.id.text)
mImageView = findViewById(R.id.imageView)
val vm = ViewModelProvider(this).get(MainActivityViewModel::class.java)
vm.getComic().observe(this, Observer {
if (it == null) {
Log.i("MainActivity", "we received null, there was an error perform inspection.")
} else {
Log.i("MainActivity", "we have alt : ${it.alt}")
mText.text = it.alt
mImageView.load(it.img)
}
})
}
}
Code language: Kotlin (kotlin)
That is it. You have learned with example on how to do GET request with Retrofit in Android. And I hope it was clear enough for everyone. Feel free to leave a comment if you are confused about anything at all.
I have purposely not used coroutines
, I will provide a separate article on how you can use coroutine
to do exactly this soon. But here is a simple coroutine explanation: Kotlin Coroutine in Android : Understanding the Basics
Until then: Keep learning.