Android Networking : Retrofit at a Glance
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'
Various HTTP
methods supported by Retrofit
- GET Request: A
GET
request s used to retrieve data from a server. Example of how to perform aGET
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: A
POST
request is used to send data to a server. Example of how to perform aPOST
request using Retrofit:
interface UserApiService{
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
}
val user = User("Romman Sabbir", "rommansabbir@gmail.com")
// 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: A
PUT
request is used to update existing resources on the server. Example of how to perform aPUT
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", "rommansabbir@gmail.com")
// 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: A
PATCH
request is similar to aPUT
request, but it is used to perform partial updates on resources. Example of how to perform aPATCH
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 "rommansabbir@gmail..com")
// 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: A
DELETE
request is used to delete resources from the server. Here's an example of how to perform aDELETE
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
}
Different Kinds of POST Method w/ Retrofit
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.
- Form URL-encoded POST request: Form URL-encoded requests are commonly used when sending data as key-value pairs, similar to how HTML forms are submitted. Here's an example of how to implement a form URL-encoded POST request using 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
}
})
- JSON POST request: JSON POST requests are commonly used when sending complex data structures or objects as JSON payloads. Here's an example of how to implement a JSON POST request using Retrofit:
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
}
})
- Multipart POST request (file upload): Multipart requests are used when uploading files to a server. We will explore Multipart POST below.
Post Image, Audio, Video w/ Retrofit
- Create an interface that defines the API endpoints for uploading media files. Assume you have a RESTful API with an endpoint
/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.
- Prepare the media files for upload
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.
- Upload the media files
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
}
})
Post File w/ Retrofit
- Create a service interface defining the API endpoints for file upload:
interface FileUploadService {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part
): Response<YourResponseModel>
}
- Create the
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)
- Make the API call to upload the file:
try {
val response = service.uploadFile(body)
if (response.isSuccessful) {
// Handle success
} else {
// Handle error
}
} catch (e: Exception) {
// Handle exception
}
File Upload Progress
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.
Using Mock Responses in Retrofit w/ MockWebServer
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:
- Mock
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()
- Mock
POST
,PUT
,PATCH
, andDELETE
Requests: MockingPOST
,PUT
,PATCH
, andDELETE
requests follow a similar pattern. You need to define the respective annotations in the API service interface and enqueue the desired mock response in theMockWebServer
.
// 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 in Retrofit from Android Assets
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 theassets
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
}
}
- Add the
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
Note:
Add
MockInterceptor
only when your application is inDEBUG
Mode.To simulate different kinds of methods calling for a single `ENDPOINT` (ex:
update_user_info
) usechain.request.method()
(ex:if (chain.request.method() == "get"
) orif (chain.request.method() == "post"
) or others (PUT
,PATCH
)
That's it for today. Happy coding...
Subscribe to my newsletter
Read articles from Romman Sabbir directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Romman Sabbir
Romman Sabbir
Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.