Nearly every modern Android app needs to communicate with a server over the internet. Building a networking layer from scratch is complex and error-prone. Thankfully, the Android community has established a clear winner for this task: the powerful combination of Retrofit and OkHttp.

Many developers use them together without fully understanding their relationship. Retrofit isn't a replacement for OkHttp; it's a brilliant abstraction layer built on top of it. Understanding how they work together will allow you to build a clean, efficient, and highly customizable networking layer.

The Relationship: Brains and Brawn

Think of their relationship like this:

  • OkHttp is the engine. It's a powerful and efficient HTTP client that handles all the low-level work: opening connections, managing caches, handling retries and redirects, and processing raw request and response data.
  • Retrofit is the clean, type-safe API on top. It's the "brains" of the operation. You define your API endpoints in a simple Kotlin interface, and Retrofit generates all the code to make those calls using OkHttp. It handles things like URL building, request body serialization, and response parsing.
You can use OkHttp by itself, but you'd have to manually build URLs, serialize JSON, and parse responses. Retrofit automates all of that, letting you focus on your app's logic.

The Basic Setup: Making Your First Call

Let's see how easy it is to fetch a list of users from a REST API.

Step 1: Add Dependencies

You'll need Retrofit, OkHttp, and a converter library (like Gson or Moshi) to handle JSON parsing.

// In app/build.gradle.kts
implementation("com.squareup.retrofit2:retrofit:2.11.0") // Use latest
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") // For logging

Step 2: Define the API Interface

This is where Retrofit's magic shines. You create a simple Kotlin interface and use annotations to describe your API endpoints.

interface ApiService {
    @GET("users")
    suspend fun getUsers(): List<User>

    @GET("users/{id}")
    suspend fun getUserById(@Path("id") userId: String): User
}

Step 3: Build the Retrofit Instance

You create a single Retrofit instance for your app, telling it the base URL of your API and which converter to use. It's common to use a dependency injection library like Hilt to provide this as a singleton.

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

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

// Now you can make the call in a coroutine
viewModelScope.launch {
    val users = apiService.getUsers()
    // ... do something with the list of users
}

That's it! Retrofit handles creating the full URL, making the network call with OkHttp, parsing the JSON response into a List<User>, and returning it, all from that simple interface definition.

The Superpower: OkHttp Interceptors

What if you need to modify every single request before it's sent? For example, adding an authentication header. This is where you drop down to the OkHttp level and use an Interceptor.

An interceptor is a powerful class that can observe, modify, and even short-circuit requests and responses. Let's create one to add an `Authorization` header.

Creating an Auth Interceptor
class AuthInterceptor(private val authToken: String) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        // Get the original request
        val originalRequest = chain.request()

        // Build a new request, adding the Authorization header
        val newRequest = originalRequest.newBuilder()
            .header("Authorization", "Bearer $authToken")
            .build()

        // Proceed with the new, modified request
        return chain.proceed(newRequest)
    }
}

Now, you just need to create an OkHttp client with this interceptor and tell Retrofit to use it.

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(AuthInterceptor("YOUR_SECRET_TOKEN"))
    .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) // Also add a logger
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient) // Tell Retrofit to use our custom client
    .addConverterFactory(GsonConverterFactory.create())
    .build()

Now, every single API call made through this Retrofit instance will automatically have the `Authorization` header attached. You can use interceptors for logging, caching, handling retries, and so much more.

By understanding that Retrofit is the user-friendly API and OkHttp is the powerful engine underneath, you can build a networking layer that is not only clean and type-safe but also incredibly flexible and robust.