Using Retrofit for Networking
1. Setting Up Retrofit Dependencies
Configure Retrofit in your Android project by adding the necessary dependencies to your build.gradle file.
| Dependency | Version | Description |
|---|---|---|
| com.squareup.retrofit2:retrofit | 2.11.0 |
Main Retrofit library for HTTP client functionality |
| com.squareup.retrofit2:converter-gson | 2.11.0 |
Gson converter for JSON parsing |
Example: Gradle Dependencies
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
}
Note: Ensure you have
internet permission in your AndroidManifest.xml for network requests.2. Creating API Interface
| Property | Description | Example |
|---|---|---|
| @GET | Defines a GET request to the specified path | @GET("users/{id}") |
| @POST | Defines a POST request to the specified path | @POST("users") |
| @PUT | Defines a PUT request to the specified path | @PUT("users/{id}") |
| @DELETE | Defines a DELETE request to the specified path | @DELETE("users/{id}") |
| @Headers | Adds custom headers to the request | @Headers("Authorization: Bearer token") |
| @Query | Defines query parameters in the URL | @GET("search") fun search(@Query("q") query: String) |
| @Path | Replaces a path variable in the URL | @GET("users/{id}") fun getUser(@Path("id") userId: Int) |
Example: Basic API Interface Definition
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@GET("posts")
suspend fun getPosts(
@Query("userId") userId: Int,
@Query("limit") limit: Int = 10
): List<Post>
}
3. Building Retrofit Instance
Example: Basic Retrofit Setup
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
| Property | Description | Values |
|---|---|---|
| baseUrl | Base URL for all API endpoints | String |
| addConverterFactory | Converter for serializing/deserializing data | Converter.Factory |
Note: Always use
GsonConverterFactory for JSON handling in Kotlin.Warning: Never hardcode API keys or sensitive data in
Retrofit configuration.4. Defining Request Parameters
| Parameter Type | Annotation | Description | Example |
|---|---|---|---|
| Path | @Path |
Replaces a path variable in the URL | |
| Query | @Query |
Adds a query parameter to the URL | |
| Query Map | @QueryMap |
Maps a map of query parameters to the URL | |
| Header | @Header |
Adds a header to the request | |
| Headers | @Headers |
Adds multiple headers to the request | |
| Body | @Body |
Sends the object as the request body | |
| Field | @Field |
Sends a field as form data (for application/x-www-form-urlencoded) |
|
| Field Map | @FieldMap |
Sends a map of fields as form data | |
Example: Using Path and Query Parameters
interface ApiService {
@GET("users/{userId}/posts")
fun getUserPosts(
@Path("userId") userId: String,
@Query("sort") sort: String = "date"
): Call<List<Post>>
}
5. Using Call Objects
| Method | Description | Return Type | Notes |
|---|---|---|---|
| enqueue() | Asynchronously executes the request and delivers the result to the callback | void |
Must be called on the main thread |
| execute() | Synchronously executes the request and returns a Response object | Response<T> |
Can block the calling thread, use with caution |
| clone() | Creates a new Call instance with the same request | Call<T> |
Useful for retrying requests |
| cancel() | Cancels the ongoing request if it has not yet completed | void |
Does not interrupt already completed requests |
| isCanceled() | Checks whether the call has been canceled | boolean |
Returns true if canceled, otherwise false |
| isExecuted() | Checks whether the call has been executed | boolean |
Returns true if executed, otherwise false |
Example: Enqueuing a Call
val call: Call<User> = apiService.getUser(123)
call.enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful) {
val user = response.body()
// Handle successful response
} else {
// Handle error response
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
// Handle network failure
}
})
Example: Synchronous Execution
val call: Call<User> = apiService.getUser(123)
try {
val response: Response<User> = call.execute()
if (response.isSuccessful) {
val user = response.body()
// Process the user data
} else {
// Handle error status code
}
} catch (e: IOException) {
// Handle network exception
}
Note: The
enqueue() method is preferred for asynchronous operations as it does not block the calling thread.
Warning: Never call
execute() on the main thread in Android apps, as it will cause an Application Not Responding (ANR) error.
6. Implementing Callbacks
| Callback Method | Description | Usage |
|---|---|---|
| onResponse | Called when the HTTP request completes successfully | override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { ... } |
| onFailure | Called when the HTTP request fails due to network issues or parsing errors | override fun onFailure(call: Call<ResponseBody>, t: Throwable) { ... } |
Example: Basic Callback Implementation
val call = apiService.getData()
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
val data = response.body()
// Process the data
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
// Handle the error
Log.e("Retrofit", "Request failed", t)
}
})
Note: Callbacks are executed on the main thread by default. For background processing, use
enqueue with a custom Executor.
7. Using Coroutines with Retrofit
| Feature | Description | Code Example |
|---|---|---|
| Suspend Functions | Used to make network calls without blocking the main thread. | |
| Coroutine Scope | Used to launch coroutine for making API calls. | |
| Error Handling | Use try-catch blocks to handle network errors. | |
Note: Retrofit 2.6+ supports suspend functions natively, simplifying coroutine usage.
8. Configuring OkHttp Client
Example: Basic OkHttp Configuration
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
| Property | Description | Default Value |
|---|---|---|
| connectTimeout | Timeout for establishing a connection | 10 seconds |
| readTimeout | Timeout for reading data from the connection | 10 seconds |
| writeTimeout | Timeout for writing data to the connection | 10 seconds |
Example: Adding Interceptors
import okhttp3.logging.HttpLoggingInterceptor
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
Note: Interceptors allow you to modify requests or responses globally, useful for logging, authentication, or caching.
9. Adding Headers
| Header Type | Usage | Notes |
|---|---|---|
| @Headers | @Headers("Cache-Control: max-age=3600", "Accept: application/json") |
Static headers applied to all requests in a method |
| @Header | @Header("Authorization") token: String |
Dynamic header value passed as parameter |
| @HeaderMap | @HeaderMap headers: Map<String, String> |
Pass multiple headers as a map |
Example: Using @HeaderMap
interface ApiService {
@GET("user/profile")
suspend fun getUserProfile(
@HeaderMap headers: Map<String, String>
): Response<UserProfile>
}
// Usage
val headers = mapOf(
"Authorization" to "Bearer $token",
"Accept" to "application/json"
)
apiService.getUserProfile(headers)
Note: Headers added via
@Headers are static and cannot be changed at runtime. Use @Header or @HeaderMap for dynamic values.10. Handling Different Response Types
| Response Type | Description | Example |
|---|---|---|
| Call<T> | Used for asynchronous requests where you need to manage the response lifecycle manually. | |
| suspend fun | Used with coroutines for asynchronous operations. More idiomatic in Kotlin. | |
| Response<T> | Provides access to the raw HTTP response including headers and status code. | |
| ResponseBody | For handling raw body content without deserialization. | |
Example: Using Response<T>
class UserApiService {
suspend fun fetchUser(userId: String): Response<User> {
return apiService.getUser(userId)
}
}
// Usage
val response = userApiService.fetchUser("123")
if (response.isSuccessful) {
val user = response.body()
// Process user
} else {
// Handle error, e.g., log status code or message
Log.e("UserApi", "Error: ${response.code()}")
}
Example: Using Call<T>
class UserApiService {
fun fetchUser(userId: String, callback: Callback<User>) {
apiService.getUser(userId).enqueue(callback)
}
}
// Usage
val callback = object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful) {
val user = response.body()
// Process user
} else {
Log.e("UserApi", "Error: ${response.code()}")
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
Log.e("UserApi", "Network failure", t)
}
}
userApiService.fetchUser("123", callback)
11. Implementing Error Handling
| Error Type | Description | Handling Strategy |
|---|---|---|
| HttpException | Thrown when the server returns an HTTP error status (e.g., 404, 500) | Check response().code() to determine the error type and handle accordingly |
| IOException | Occurs during network request execution (e.g., timeout, no internet) | Use connectTimeout and readTimeout to manage retries or show user-friendly messages |
| JsonSyntaxException | Occurs when Retrofit fails to parse JSON response | Log parsing errors and provide fallback responses or user feedback |
Example: Handling HttpException
val response = try {
apiService.getData()
} catch (e: HttpException) {
when (e.code()) {
404 -> "Resource not found"
500 -> "Server error occurred"
else -> "Unexpected HTTP error: ${e.code()}"
}
}
Example: Handling IOException
val response = try {
apiService.getData()
} catch (e: IOException) {
"Network error occurred. Please check your connection."
}
Note: Use
OkHttpClient interceptors to centralize error handling logic across all requests.
Error Handling Best Practices
- Implement specific error handling for
HttpExceptionbased on HTTP status codes - Wrap network calls in
try-catchblocks to handleIOException - Log errors for debugging but avoid exposing sensitive information to users
- Provide user-friendly messages instead of raw error codes or stack traces