OkHttp & Interceptors
The last lesson built a TrailApiService interface and a Retrofit
instance, but Retrofit itself never actually opens a network socket. It generates the request
description — method, URL, headers, body — and hands that off to OkHttp, the HTTP
client that does the real work: opening connections, writing bytes, and reading the response back.
Retrofit handles the type-safe API definition; OkHttp handles the actual network request. This
lesson works one layer down, at the OkHttp level.
What an Interceptor Is
An interceptor is a piece of code that sits in the middle of every request OkHttp sends and every response it receives, with the ability to inspect or modify either one before it continues on its way. Interceptors chain together — each one gets a chance to look at (or change) the request, pass it along, then look at (or change) the response coming back. It's the single most useful extension point OkHttp offers, and TrailLog puts it to real use in two specific ways below.
A Logging Interceptor for Development
HttpLoggingInterceptor, from the okhttp3:logging-interceptor artifact,
prints full request and response details to Logcat — URL, headers, and body — which is genuinely
invaluable while building and debugging TrailLog's networking code (see also
Logging & Debugging Tools for Logcat itself):
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
Level.BODY logs everything, including the response payload — extremely useful during
development, but something you'd typically dial back (or strip out entirely in release builds) once
an app ships, since it can print sensitive data straight into device logs.
An Auth Interceptor: Attaching a Bearer Token Automatically
A genuinely common real-world need: TrailLog's backend requires every authenticated request to
carry an Authorization header. Rather than remembering to add that header at every
single call site in TrailApiService, an interceptor can attach it once, automatically,
to every outgoing request:
class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider()
val original = chain.request()
val request = if (token != null) {
original.newBuilder()
.header("Authorization", "Bearer $token")
.build()
} else {
original
}
return chain.proceed(request)
}
}
tokenProvider is a small lambda supplying the current token on demand, so this
interceptor never has to know where the token actually comes from — it just reads whatever value is
current at the moment each request goes out. Once Firebase
Auth is introduced later in the track, this is exactly where that token gets plugged in.
Building the OkHttpClient and Wiring It Into Retrofit
Both interceptors get attached when building a custom OkHttpClient, which is then
passed into Retrofit's builder — tying this lesson directly to the previous one:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor { getCurrentAuthToken() })
.addInterceptor(loggingInterceptor)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.traillogapp.example.com/")
.client(okHttpClient)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
Interceptor order matters: as written, the auth header gets attached first, then the logging interceptor runs and prints the request (including that header) to Logcat before it's actually sent.
Timeouts: Why Sensible Defaults Matter on Mobile
connectTimeout caps how long OkHttp waits to establish a connection; readTimeout
caps how long it waits for data once connected. Mobile networks are genuinely unreliable — a phone
moving from Wi-Fi to cellular, or hiking through an area with a weak signal, is exactly the kind of
situation TrailLog's users are actually in. Leaving timeouts unset means OkHttp's own defaults apply
(10 seconds for both), but setting them explicitly, as above, makes the behavior a deliberate choice
rather than an accident — long enough to tolerate a slow connection, short enough that a hung
request doesn't leave the UI stuck indefinitely.
A Brief Note on Response Caching
OkHttp also supports HTTP response caching out of the box — configuring a Cache with a
directory and a size limit lets OkHttp honor a server's caching headers and serve some responses
from disk without hitting the network again at all. It's worth knowing this exists as a
complementary layer to the Room-based caching in TrailRepository, though the deliberate
offline-fallback logic built in that repository already covers TrailLog's actual caching needs — this
is conceptual awareness for when a project needs HTTP-level caching specifically.
Trail.