Android: Master Retrofit

Manuchekhr Tursunov
5 min readApr 1, 2023

--

Retrofit is a popular HTTP client library for Android and Java-based applications, which simplifies making HTTP requests and processing their responses. It uses annotations to define the request parameters and the response structure.

Retrofit is based on the four pillars of HTTP request-response cycle:

  1. Request method The HTTP request method, also known as a verb, is used to specify the desired action to be performed on the resource. Retrofit supports all HTTP request methods such as GET, POST, PUT, DELETE, etc.
  2. Endpoint An endpoint is a URL that specifies the location of a resource. Retrofit uses annotations to specify the endpoint for each API call.
  3. Request body The request body is the data sent to the server as part of the API call. In Retrofit, you can use annotations to specify the type of data being sent in the request body.
  4. Response body The response body is the data received from the server as a result of the API call. In Retrofit, you can define the expected response type using annotations.

These four pillars work together to provide a simple and powerful way to interact with REST APIs in Android applications. By using Retrofit, developers can easily define their API calls and handle the request and response data with minimal boilerplate code.

Here is a sample code that demonstrates the usage of Retrofit in Kotlin:

  1. Add the following dependencies to your app-level build.gradle file:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

2. Create a Retrofit instance with a base URL:

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()

3. Define an interface that defines the endpoints of the API:

interface ApiService {
@GET("users/{user}/repos")
fun listRepos(@Path("user") user: String): Call<List<Repo>>
}

4. Create an instance of the interface using the Retrofit instance:

val apiService = retrofit.create(ApiService::class.java)

5. Make a request using the interface instance:

val call = apiService.listRepos("octocat")
call.enqueue(object : Callback<List<Repo>> {
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
if (response.isSuccessful) {
val repos = response.body()
// Do something with the repos
} else {
// Handle error
}
}

override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
// Handle error
}
})

In the example above, we defined an interface ApiService that has one endpoint listRepos, which returns a list of Repo objects. We then created an instance of the interface using the Retrofit instance, and made a request to the endpoint using the enqueue method of the Call object.

Retrofit also supports many other features, such as request/response interception, request cancellation, and custom call adapters. By using Retrofit, you can greatly simplify the process of making HTTP requests and processing their responses in your Android or Java-based application.

Working of Retrofit under the hood:

When you create a Retrofit instance, it uses a series of interceptors to process network requests and responses. These interceptors operate in a pipeline fashion, each performing a specific function on the request or response before passing it to the next interceptor in the pipeline. Here is an example of creating a Retrofit instance with a custom OkHttpClient and logging interceptor:

val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()

val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()

In the above code, we create an instance of OkHttpClient and add a logging interceptor to it using the HttpLoggingInterceptor class. This interceptor logs the network requests and responses to the console for debugging purposes.

We then create a Retrofit instance using the Retrofit.Builder() class and pass in the base URL of the API we want to consume. We also pass in our custom okHttpClient instance, which contains the logging interceptor. Finally, we add a GsonConverterFactory to handle deserialization of JSON responses into Kotlin objects.

Once we have a Retrofit instance, we can define an interface that defines the endpoints we want to call on the API. Here is an example interface:

interface MyApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): Response<User>
}

In the above code, we define a MyApiService interface with a single endpoint called getUser. This endpoint performs a GET request to the URL users/{id}, where {id} is replaced with an integer value passed as a parameter to the function. The suspend keyword indicates that this function is a coroutine that can be called asynchronously.

The return type of the function is a Response<User>, which is a Retrofit class that wraps the HTTP response from the server. This class contains information about the response status, headers, and body. We specify the type of the response body as User, which is a Kotlin data class that represents the JSON response from the server.

To call this endpoint, we can create an instance of the MyApiService interface using the retrofit.create() method:

val myApiService = retrofit.create(MyApiService::class.java)
val response = myApiService.getUser(1)

In the above code, we create an instance of the MyApiService interface and call the getUser function with an ID of 1. This returns a Response<User> object, which we can then use to extract the user data from the response body.

Additional examples working with Retrofit:

  • Adding Headers to Requests:

You can add headers to a request by using the @Headers annotation in your Retrofit interface. Here's an example:

interface ApiService {
@Headers("Authorization: token {your_token}")
@GET("user/repos")
fun listRepos(): Call<List<Repo>>
}

In this example, we’re adding an Authorization header with a token to the request.

  • Query Parameters:

You can add query parameters to a request by using the @Query annotation in your Retrofit interface. Here's an example:

interface ApiService {
@GET("search/repositories")
fun searchRepos(@Query("q") query: String): Call<SearchResponse>
}

In this example, we’re adding a query parameter with the name “q” and the value of the query parameter.

  • Dynamic URLs:

You can use dynamic URLs in Retrofit by using the @Url annotation in your method parameter. Here's an example:

interface ApiService {
@GET
fun getUser(@Url url: String): Call<User>
}

In this example, we’re passing in a dynamic URL as a parameter to the getUser method.

  • Customizing Request Body Encoding:

You can customize how the request body is encoded by using the @Body annotation in your method parameter and a custom Converter object. Here's an example:

interface ApiService {
@POST("users")
fun createUser(@Body user: User): Call<User>
}

class MyConverterFactory : Converter.Factory() {
override fun requestBodyConverter(
type: Type,
parameterAnnotations: Array<Annotation>,
methodAnnotations: Array<Annotation>,
retrofit: Retrofit
): Converter<*, RequestBody>? {
// Customize request body encoding here
}
}

val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(MyConverterFactory())
.build()

In this example, we’re using a custom Converter object to customize how the request body is encoded for the createUser method.

  • Handling Error Responses:

You can handle error responses in Retrofit by using the onFailure method on the Call object. Here's an example:

val call = apiService.listRepos()
call.enqueue(object : Callback<List<Repo>> {
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
if (response.isSuccessful) {
// Handle successful response
} else {
// Handle error response
}
}

override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
// Handle network error
}
})

In this example, we’re using the onResponse method to handle the response from the server and the onFailure method to handle network errors.

Overall, Retrofit is a powerful library that simplifies the process of consuming RESTful APIs in Kotlin. It handles much of the low-level networking code for you, allowing you to focus on defining the API endpoints and handling the responses in a clean and efficient manner.

--

--