OkHttp Interceptor in Android

Anna
7 min readSep 8, 2024

--

What are Interceptors?

OkHttp Interceptors in Android are powerful tools that allow you to intercept and modify HTTP requests and responses. They provide a flexible way to customize network behavior, add custom headers, log requests and responses, implement authentication mechanisms, and perform other network-related tasks.

In simple words, Interceptors are like the security personnel in the security check process at the Airport. They check our boarding pass, put a stamp on it, and then allow us to pass.

Types of Interceptors:

  • Application Interceptors: These are interceptors that are added between the Application Code(our written code) and the OkHttp Core Library. These are the ones that we add using addInterceptor().
  • Network Interceptors: These are interceptors that are added between the OkHttp Core Library and the Server. These can be added to OkHttpClient using addNetworkInterceptor().

How to add interceptors in OkHttpClient?

  • While building the OkHttpClient object, we can add the interceptor as below:
fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(/*our interceptor*/)
return builder.build()
}

Here, in addInterceptor, we pass the interceptor that we have created. Now, let's see how to create the Interceptor.

Creating the Interceptor

To create the interceptor, we need to create a class by implementing the Interceptor interface as below:

class MyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
/**
* Our API Call will be intercepted here
*/
}
}

Here, in the intercept(), we can perform any action which we want inside it.

And to use the interceptor, we can use as below:

fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(MyInterceptor())
return builder.build()
}

We can add the MyInterceptor in addInterceptor().

Now, let’s discuss more real use cases where we can use the Interceptors.

Real use-cases using the Interceptors

The following are the common use cases in Android:

Logging the errors centrally

First, we need to create the ErrorInterceptor as below:

class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response = chain.proceed(request)
when (response.code()) {
400 -> {
//Show Bad Request Error Message
}
401 -> {
//Show UnauthorizedError Message
}
403 -> {
//Show Forbidden Message
}
404 -> {
//Show NotFound Message
}
// ... and so on
}
return response
}
}
  • First, we get the request from the chain.request()
  • Then, we get the response returned from the server, by passing the request in the chain.proceed(request)
  • Now, we can check for the response code and perform an action.
  • We can pass the error to the view.
  • Let’s say we get a 401 error i.e. Unauthorized then we can perform an action to clear the app data/log out the user or any action which we want to perform.

Now, to use this ErrorInterceptor in our OkHttpClient, we can add as below:

.addInterceptor(ErrorInterceptor())

This is how we can create a centralized Error Logger using the Interceptor.

OkHttp has a built-in logger which is very useful for debugging.

Note: If we want to log the details for the redirection of the URL, consider using the interceptor at the network layer using the addNetworkInterceptor().

Caching the response

If we want to cache the response of the API call so that if we call the API again, the response comes out from Cache.

Let’s say we have the API call from Client to Server and Cache-Control header is enabled from the server, then OkHttp Core will respect that header and cache the response for a certain time being which was sent from the server.

But what if the Cache-Control is not enabled from the server? We still can cache the response from OkHttp Client using Interceptor.

Just see the above image. Here, what we have to do is that we have to intercept the Response before going inside the OkHttp Core and add the header (Cache-Control), so it will be treated as if the response(with the Cache-Control header) has come from the server. Okhttp Core will respect that and cache the response.

We will create an interceptor as below:

class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response: Response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(10, TimeUnit.DAYS)
.build()
return response.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build()
}
}

Here, we have a CacheControl which is used to provide the header for Cache-Control.

Finally, we can add as below:

val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) // 10 MiB
.addNetworkInterceptor(CacheInterceptor())
.build();

Here, if we see, we are not using the addInterceptor() but using addNetworkInterceptor() for the use case. This is because in this case, the operation is happening at the network layer.

But, there is something important, we need to consider while building the offline-first app.

The cached response will be returned only when the Internet is available as OkHttp is designed like that.

  • When the Internet is available and data is cached, it returns the data from the cache.
  • Even when the data is cached and the Internet is not available, it returns the error “no internet available”.

What to do now?

We can use the following ForceCacheInterceptor at the Application layer in addition to the above one (CacheInterceptor, only if not enabled from the server). To implement in the code, we will create a ForceCacheInterceptor as below:

class ForceCacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder: Request.Builder = chain.request().newBuilder()
if (!IsInternetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}

We can add the Interceptor in OkHttpClient as below:

val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) // 10 MiB
.addNetworkInterceptor(CacheInterceptor()) // only if Cache-Control header is not enabled from the server
.addInterceptor(ForceCacheInterceptor())
.build();

Here, we are adding the ForceCacheInterceptor to OkHttpClient using addInterceptor() and not addNetworkInterceptor() as we want it to work on the Application layer.

Adding the Header like Access Token centrally

Let’s say that we have to make the API calls and we have to add the Authorization Header in all the API calls. Either we can use it individually or we can centralize that using the Interceptor.

class AuthTokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Authorization", "AuthToken")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
  • First, we get the token for the header from our local storage like a SharedPreference.
  • Here, we intercept the original request which we triggered from the application using chain.request() and set it to originalRequest.
  • Then, we build the request again by adding the Header with the key and value which is required to make the network call.
  • Then, we will build the request again and return the response using chain.proceed(request) by passing the new request which is having the Authorization header.

This is how we can centralize the Header which is common in all the API Calls. Now to use it in the OkHttpClient, we will do as below:

.addInterceptor(AuthTokenInterceptor())

Let’s go to another use case.

Refreshing the Access Token at Single Place

Let us say we have a use-case that when we get a 401 error in the Error Interceptor and we need to refresh the auth token as we have an Unauthorized error. We can do that using the below:

override fun intercept(chain: Interceptor.Chain): Response {
val accessToken = //our access Token
val request = chain.request().newBuilder()
.addHeader("Authorization", accessToken)
.build()
val response = chain.proceed(request)
if (response.code() == 401) {
val newToken: String = //fetch from some other source
if (newToken != null) {
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", newToken)
.build()
return chain.proceed(newRequest)
}
}
return response
}

If we get the response.code() as 401 i.e. Unauthorized, we will refresh the token here, then modify the request by adding the new header and make the new request to the server.

And, this should be synchronized to avoid the concurrency issue.

Note: Another way that is more flexible when it comes to refreshing the Access Token is to use the Authenticator interface of OkHttp.

Now, let’s move to another use case.

Enabling Gzip at Android End

Gzip is used for data compression. In Android as well, we can use the Gzip for the compression using the interceptor.

So, while getting a response, OkHttp automatically respects the header (content encoding) and decompresses the data and returns, but let’s say when we have to send compressed data to a server, then we have to write our own interceptor.

We can create the GzipRequestInterceptor class as below (taken from the OkHttp repository).

public class GzipRequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}

Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), forceContentLength(gzip(originalRequest.body())))
.build();
return chain.proceed(compressedRequest);
}

private RequestBody forceContentLength(final RequestBody requestBody) throws IOException {
final Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
return new RequestBody() {
@Override
public MediaType contentType() {
return requestBody.contentType();
}

@Override
public long contentLength() {
return buffer.size();
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(buffer.snapshot());
}
};
}


private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}

@Override
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}

To use the interceptor we can use as below:

.addInterceptor(GzipRequestInterceptor())

So, these are the real use cases, how can we use the interceptors in our Android App. We can do a lot of things with interceptors. Let’s start making the most of it.

Downloading a news list as an example

Full source code:

Demo:

Downloading a news list

By using OkHttp Interceptors, you can create powerful and customized network behaviors in your Android applications.

If you find errors or you think something important is missing, please report them in the comments. Also, let us know what other scenarios you would like us to write about.

Thanks for Reading ❤

Anna

--

--

No responses yet