Android Networking : Retrofit at a Glance
Unleashing the Power of Retrofit for Seamless Communication and Enhanced User Experience

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
Unleashing the Power of Retrofit for Seamless Communication and Enhanced User Experience

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
No comments yet. Be the first to comment.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them — in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
In the realm of Android development, Retrofit has emerged as a go-to library for handling API communication. Its simplicity, flexibility, and powerful features make it an ideal choice for interacting with web services. In this blog post, we will dive deep into Retrofit and explore how to use it for different HTTP methods, including GET, POST, PUT, PATCH, and DELETE. Additionally, we'll discover the concept of mock responses and learn how to use them with Retrofit. All examples will be written in Kotlin.
To get started with Retrofit, add the following dependency to your project's build.gradle file:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
To enable support for Gson serialization/deserialization also includes the Gson converter dependency:
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
HTTP methods supported by RetrofitGET request s used to retrieve data from a server. Example of how to perform a GET request using Retrofit:interface UserApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: String): Response<User>
}
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(UserApiService::class.java)
// Making the GET request
val response = apiService.getUser(999)
if (response.isSuccessful) {
val user = response.body()
// Handle the user data
} else {
// Handle the error
}
POST request is used to send data to a server. Example of how to perform a POST request using Retrofit:interface UserApiService{
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
}
val user = User("Romman Sabbir", "[email protected]")
// Making the POST request
val response = apiService.createUser(user)
if (response.isSuccessful) {
val createdUser = response.body()
// Handle the created user data
} else {
// Handle the error
}
PUT request is used to update existing resources on the server. Example of how to perform a PUT request using Retrofit:interface UserApiService{
@PUT("users/{id}")
suspend fun updateUser(@Path("id") userId: String, @Body updatedUser: User): Response<User>
}
val updatedUser = User("Romman Sabbir", "[email protected]")
// Making the PUT request
val response = apiService.updateUser(999, updatedUser)
if (response.isSuccessful) {
val updatedUser = response.body()
// Handle the updated user data
} else {
// Handle the error
}
PATCH request is similar to a PUT request, but it is used to perform partial updates on resources. Example of how to perform a PATCH request using Retrofit:interface UserApiService{
@PATCH("users/{id}")
suspend fun updateUser(@Path("id") userId: String, @Body updatedFields: Map<String, String>): Response<User>
}
val updatedFields = mapOf("email" to "[email protected]")
// Making the PATCH request
val response = apiService.updateUser(999, updatedFields)
if (response.isSuccessful) {
val updatedUser = response.body()
// Handle the updated user data
} else {
// Handle the error
}
DELETE request is used to delete resources from the server. Here's an example of how to perform a DELETE request using Retrofit:interface UserApiService{
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: String): Response<Unit>
}
// Making the DELETE request
val response = apiService.deleteUser(999)
if (response.isSuccessful) {
// Handle the successful deletion
} else {
// Handle the error
}
Retrofit supports various types of POST methods to send data to a server. Let's explore different types of POST methods and how to implement them using Kotlin with Retrofit.
Define the API service interface:
interface ApiService {
@FormUrlEncoded
@POST("endpoint")
fun sendData(
@Field("param1") param1: String,
@Field("param2") param2: String
): Call<ResponseBody>
}
Make the POST request:
val call = apiService.sendData("value1", "value2")
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
// Request successful
} else {
// Handle error
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
// Handle failure
}
})
Define the API service interface:
interface ApiService {
@Headers("Content-Type: application/json")
@POST("endpoint")
fun sendData(@Body requestBody: RequestBody): Call<ResponseBody>
}
We specify the "Content-Type" header as "application/json" using the @Headers annotation. The request body is represented by the @Body annotation, and we pass a RequestBody object.
Make the POST request:
val requestBody = RequestBody.create(MediaType.parse("application/json"), json)
val call = apiService.sendData(requestBody)
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
// Request successful
} else {
// Handle error
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
// Handle failure
}
})
/upload that accepts a media file. Create a new Kotlin file, e.g., MediaService.kt, and define the service interface as follows:interface MediaService {
@Multipart
@POST("/upload")
fun uploadMedia(
@Part("image\"; filename=\"image.jpg\"") image: RequestBody?,
@Part("audio\"; filename=\"audio.mp3\"") audio: RequestBody?,
@Part("video\"; filename=\"video.mp4\"") video: RequestBody?
): Call<ResponseBody>
}
We have a single API endpoint /upload that accepts three different media files: image, audio, and video. Each file is represented by a RequestBody object annotated with the @Part annotation. The filename parameter specifies the desired filename for each file.
Before uploading the media files, we need to prepare them for upload. Assuming you have the file paths or URIs for each file, you can create RequestBody objects from them. Here's an example of how to do it:
val imageFile = File(imageFilePath)
val imageRequestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
val audioFile = File(audioFilePath)
val audioRequestBody = audioFile.asRequestBody("audio/*".toMediaTypeOrNull())
val videoFile = File(videoFilePath)
val videoRequestBody = videoFile.asRequestBody("video/*".toMediaTypeOrNull())
imageFilePath, audioFilePath, and videoFilePath represent the paths to the respective media files. We create RequestBody objects using the asRequestBody extension function, specifying the media type based on the file extension.
Now that everything is set up, you can upload the media files using the Retrofit service interface. Make the API call by invoking the uploadMedia method and passing the respective RequestBody objects as parameters:
val call = mediaService.uploadMedia(imageRequestBody, audioRequestBody, videoRequestBody)
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
// Media upload successful
} else {
// Handle error
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
// Handle failure
}
})
interface FileUploadService {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part
): Response<YourResponseModel>
}
RequestBody for your file:val file = File("path_to_your_file")
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file)
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
try {
val response = service.uploadFile(body)
if (response.isSuccessful) {
// Handle success
} else {
// Handle error
}
} catch (e: Exception) {
// Handle exception
}
Add OkHttp dependency:
implementation("com.squareup.okhttp3:okhttp:4.10.0")
To track the progress of the file upload, we can use an OkHttpClient instance with a custom Interceptor that implements the ProgressListener interface. This listener receives progress updates during the upload process. Here's an example of how we can implement it:
class ProgressInterceptor(private val progressListener: ProgressListener) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
val requestBody = originalRequest.body
if (requestBody != null) {
val progressRequestBody = ProgressRequestBody(requestBody, progressListener)
requestBuilder.method(originalRequest.method, progressRequestBody)
}
val request = requestBuilder.build()
return chain.proceed(request)
}
}
interface ProgressListener {
fun onProgress(progress: Int)
}
The ProgressInterceptor intercepts the network request and wraps the request body with a ProgressRequestBody. The ProgressListener interface provides a callback to track the progress of the upload.
To use the ProgressInterceptor, modify the creation of the OkHttpClient instance as follows:
val progressInterceptor = ProgressInterceptor(object : ProgressListener {
override fun onProgress(progress: Int) {
// Update the progress here
}
})
val httpClient = OkHttpClient.Builder()
.addInterceptor(progressInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("http://example.com/")
.client(httpClient)
.build()
The ProgressInterceptor is added to the OkHttpClient instance using the addInterceptor method. This allows the interceptor to track the progress of the file upload. The onProgress method in the ProgressListener interface is called with the progress value during the upload process, allowing us to update the progress as needed.
Mock responses are incredibly useful during testing and development, allowing you to simulate server responses without relying on an actual backend. Retrofit provides the MockWebServer class for creating a local HTTP server that responds with pre-defined mock responses. Here's how you can use mock responses with each HTTP method in Retrofit:
GET Request:val server = MockWebServer()
server.enqueue(MockResponse().setBody("This is a simple mock response."))
// Start the server and make the request
server.start()
val baseUrl = server.url("/")
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
// Define the API service
interface MockUserApiService {
@GET("data")
suspend fun getData(): Response<String>
}
val apiService = retrofit.create(MockUserApiService::class.java)
// Make the GET request
val response = apiService.getData()
if (response.isSuccessful) {
val responseData = response.body()
// Handle the mock response data
} else {
// Handle the error
}
// Shutdown the server
server.shutdown()
POST, PUT, PATCH, and DELETE Requests: Mocking POST, PUT, PATCH, and DELETE requests follow a similar pattern. You need to define the respective annotations in the API service interface and enqueue the desired mock response in the MockWebServer.// Similar steps as the GET request, just replace the annotation in the API service interface
interface MockApiService {
@POST("data")
suspend fun postData(@Body requestBody: RequestBody): Response<String>
// Other mock methods here
}
Mock responses are invaluable during testing and development, enabling you to simulate server responses without relying on a live backend. One approach is to load mock responses from the Android assets folder. Here's how you can incorporate mock responses using Retrofit:
Create a folder named mock_responses in the assets directory of your Android project.
Save the mock response files as JSON files in the mock_responses folder. For example, get_user_success.json might contain the mock response for the GET request to retrieve a user's data.
Implement a custom Interceptor that intercepts the HTTP requests and returns the corresponding mock response if it matches the request's URL.
class MockInterceptor(private val context: Context) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = getMockResponse(request.url.toString())
return response ?: chain.proceed(request)
}
private fun getMockResponse(requestUrl: String): Response? {
val path = requestUrl.substring(BASE_URL.length) // Adjust as per your base URL structure
val fileName = "mock_responses/$path.json"
val inputStream = context.assets.open(fileName)
val source = inputStream.source().buffer()
val mockResponse = Response.Builder()
.code(200)
.protocol(Protocol.HTTP_1_1)
.message("Mock response")
.body(ResponseBody.create("application/json".toMediaTypeOrNull(), source.readString(Charsets.UTF_8)))
.build()
inputStream.close()
return mockResponse
}
}
MockInterceptor to your Retrofit instance when building it:val interceptor = MockInterceptor(context) // Replace `context` with your application context
val httpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
// Use the Retrofit instance as usual
Add MockInterceptor only when your application is in DEBUG Mode.
To simulate different kinds of methods calling for a single `ENDPOINT` (ex: update_user_info) use chain.request.method() (ex: if (chain.request.method() == "get") or if (chain.request.method() == "post") or others (PUT, PATCH)
That's it for today. Happy coding...