Skip to main content

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
@GET("users/{userId}/posts")
fun getUserPosts(@Path("userId") userId: String): Call<List<Post>>
Query @Query Adds a query parameter to the URL
@GET("search")
fun searchUsers(@Query("q") query: String): Call<List<User>>
Query Map @QueryMap Maps a map of query parameters to the URL
@GET("search")
fun searchUsers(@QueryMap options: Map<String, String>): Call<List<User>>
Header @Header Adds a header to the request
@GET("profile")
fun getProfile(@Header("Authorization") token: String): Call<User>
Headers @Headers Adds multiple headers to the request
@Headers(
    "Accept: application/json",
    "User-Agent: MyApp"
)
@GET("profile")
fun getProfile(): Call<User>
Body @Body Sends the object as the request body
@POST("users")
fun createUser(@Body user: User): Call<User>
Field @Field Sends a field as form data (for application/x-www-form-urlencoded)
@FormUrlEncoded
@POST("login")
fun login(@Field("username") username: String, @Field("password") password: String): Call<AuthResponse>
Field Map @FieldMap Sends a map of fields as form data
@FormUrlEncoded
@POST("users")
fun createUser(@FieldMap fields: Map<String, String>): Call<User>

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.
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
}
Coroutine Scope Used to launch coroutine for making API calls.
lifecycleScope.launch {
    val user = apiService.getUser(1)
    // Handle result
}
Error Handling Use try-catch blocks to handle network errors.
lifecycleScope.launch {
    try {
        val user = apiService.getUser(1)
        // Handle success
    } catch (e: Exception) {
        // Handle error
    }
}
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.
interface ApiService {
    @GET("users/{id}")
    fun getUser(@Path("id") id: String): Call<User>
}
suspend fun Used with coroutines for asynchronous operations. More idiomatic in Kotlin.
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}
Response<T> Provides access to the raw HTTP response including headers and status code.
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: String): Response<User>
}
ResponseBody For handling raw body content without deserialization.
interface ApiService {
    @GET("users/{id}/avatar")
    suspend fun getAvatar(@Path("id") id: String): ResponseBody
}

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 HttpException based on HTTP status codes
  • Wrap network calls in try-catch blocks to handle IOException
  • Log errors for debugging but avoid exposing sensitive information to users
  • Provide user-friendly messages instead of raw error codes or stack traces

Was this article helpful?