Intercepting Network Requests in Android
via OkHttp Interceptor.

Intercepting Network Requests in Android via OkHttp Interceptor.

If you are making network requests in your android app then I am pretty sure that you must be using Retrofit to do that. While Retrofit just makes requests it is also important to monitor these requests which can be achieved by using Interceptors.

Retrofit is a wrapper over the OkHttp library which is developed by Square to make the job of making network requests easier.

Why Use Interceptor?

The most real use case I discovered recently when I started my Android Developer Internship. For every new feature, new end points of the product's api has to be made to provide data to the app. Till the api is not ready for use the Android team uses the Interceptor to mimic the network request and provides dummy responses so that development work can continue.

Project Description - Using OkHttp Interceptor to test an api which is not yet available to be used in production by providing test responses for network requests.

Base Url - experimentapi.com End Point - feature

You can provide any Base Url and End Point as the whole purpose of this blog is to use an api which is not available for use yet.

Create a new Android Studio Project. In the app level build.gradle file add the following dependencies

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'

// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"

Also add the Internet Permission in the Android Manifest file <uses-permission android:name="android.permission.INTERNET"/>

Layout file for the project

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_activity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Hello World!"
        android:textColor="@color/black"
        android:textSize="32sp"
        app:layout_constraintBottom_toTopOf="@+id/bt_request"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_request"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Get"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_activity" />
</androidx.constraintlayout.widget.ConstraintLayout>

This is a simple layout which contains a button and a textview, we will make a network call by clicking the button and show the data in the textview.

Before proceeding let's first add a test response in the project which the Interceptor will provide after intercepting the request

Follow the steps given to make a directory called raw

image.png

In this directory make a file with name testresponse.json

image.png

In this file paste the response which you want to provide in the json format. Like this

{
  "title": "Feature Title",
  "id": "Feature Id"
}

Let's get to the main part of writing our interceptor class

Make a new Kotlin class and name it DemoInterceptor and Inherit it from Interceptor class, implement the members and it should look like this

package com.example.interceptordemo

import okhttp3.Interceptor
import okhttp3.Response

class DemoInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        TODO("Not yet implemented")
    }
}

Our aim is to intercept the network request experimentapi.com/feature and provide the response inside testresponse.json

package com.example.interceptordemo

import okhttp3.Interceptor
import okhttp3.Response

class DemoInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        val uri = chain.request().url.toUrl()

        when {
            uri.toString()
                .endsWith("feature") -> {

                // provide test response

            }
        }
        return chain.proceed(chain.request())
    }
}
  1. File testresponse.json is inside resources directory located in the folder raw and to access it we will need the application context.

  2. Also we need to parse the .json file in the response that we are returning.

package com.example.interceptordemo

import android.content.Context
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.io.BufferedReader

class DemoInterceptor(private val context: Context) : Interceptor {

    private var contentType = "application/json"

    override fun intercept(chain: Interceptor.Chain): Response {

        val uri = chain.request().url.toUrl()

        when {
            uri.toString()
                .contains("https://www.experimentapi.com/feature") -> {
                return getResponse(chain, R.raw.testresponse)
            }
        }
        return chain.proceed(chain.request())
    }

    fun getResponse(chain: Interceptor.Chain, resId: Int): Response {
        val jsonString = this.context.resources
            .openRawResource(resId)
            .bufferedReader()
            .use(BufferedReader::readText)

        val builder = Response.Builder()
        builder.request(chain.request())
        builder.protocol(Protocol.HTTP_2)
        builder.addHeader("content-type", contentType)
        builder.body(
            jsonString.
            toByteArray().
            toResponseBody(contentType.toMediaTypeOrNull())
        )
        builder.code(200)
        builder.message(jsonString)
        return builder.build()
    }
}

This is the final DemoInterceptor class.

builder.code(200) represents that a successful request.

We need a class that maps the our json response

package com.example.interceptordemo
/**
 * Data class to for testresponse.json
 * */
data class FeatureResponse(
    val id: String,
    val title: String
)

Let's make our api interface

package com.example.interceptordemo

import retrofit2.Response
import retrofit2.http.GET

interface ExperimentApi {
    // api we want to test
    @GET("feature")
    suspend fun getFeature(): Response<FeatureResponse>
}

Note - suspend keyword tell the compiler that this function will be called from a coroutine only. Discussion about Coroutines is not in the scope of this article

Now what is left is to just make an api call with retrofit, which we will do inside our MainActivity.class.

package com.example.interceptordemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.example.interceptordemo.databinding.ActivityMainBinding
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val builder = OkHttpClient()
            .newBuilder()
            .addInterceptor(DemoInterceptor(applicationContext))
            .build()

        val experiment_api by lazy {
            Retrofit.Builder()
                .baseUrl("https://www.experimentapi.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(builder)
                .build()
                .create(ExperimentApi::class.java)
        }

        binding.btRequest.setOnClickListener {

            lifecycleScope.launch {
                Log.e("info", Thread.currentThread().toString())
                val response = experiment_api.getFeature()
                binding.tvActivity.text = response.body()?.title
            }
        }
    }
}

In the above class:

  1. We first built an instance of our OkHttp client and added our DemoInterceptor to with context to it.

  2. Then we provided our OkHttp client to our retrofit instance.

  3. Lastly we added a click listener to our button which makes a network call by retrofit and puts the data in the textview.

Note - lifecycleScope is used to make sure that main thread doesn't gets blocked while making network request and to manage the lifecycle.

We have successfully completed our project and here is the link of github repository github.com/avidraghav/Interceptor-Demo

If you liked the blog than do comment or react. Any Feedback will be highly appreciated 😄

Connect with me on LinkedIn

Twitter (@avidRaghav)

My Website - avidraghav.dev