OkHttp Interceptors in Android

May 25 2021 · Kotlin 1.4, Android 5, Android Studio 4.1

Part 1: Implementing OkHttp Interceptors

06. Implement an Analytics Interceptor

Episode complete

About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 05. Implement an Authorization Interceptor

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 06. Implement an Analytics Interceptor

Try creating an Interceptor that retries an API call if it fails just once.

Progressively, start moving network operations common to all API calls to an Interceptor.

To check the official documentation, head over to https://square.github.io/okhttp/interceptors/.

Transcript: 06. Implement an Analytics Interceptor

Intro

When releasing apps to production, it is helpful to have analytics data. Analytics can help you learn more about your users as well as narrow down the scope when debugging an issue.

You can visualize network analytics as follows: 1. Your Repository or ViewModel makes an API call but is unaware of any analytics. 2. You intercept the API call and add all required headers. 3.The remote server receives a request along with all required analytics data and logs it.

Let’s see how to implement analytics interceptors.

Start by creating a new file in the interceptor package and naming it AnalyticsInterceptor. dd the following code:

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

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

    }
}

This code creates a class named AnalyticsInterceptor and extends the Interceptor interface. It also takes a Context instance as a constructor parameter. Next, add the headers keys you want to track:

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

    private val APP_VERSION = "X-App-Version"
    private val DEVICE_MODEL = "X-Device-Model"
    private val DEVICE_PLATFORM = "X-Device-Platform"
    private val OS_VERSION = "X-Device-OS-Version"

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

In this case, you are adding headers to track the app version, device model, device platform and the OS version.

Now, create a new Request.Builder using the existing request as follows:

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()
    }
}

To get the app’s version, you have to first get access to the app’s package info.

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        var version = packageInfo.versionName
    }
}

Next, add the headers along with required information and proceed with the new request. The first header is for the app version.

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        var version = packageInfo.versionName

        requestBuilder.addHeader(APP_VERSION, version)
    }
}

Next, add the header for the OS version. Since the OS version is a runtime value, you get it from the Build class as follows:

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        var version = packageInfo.versionName

        requestBuilder.addHeader(APP_VERSION, version)
        requestBuilder.addHeader(OS_VERSION, Build.VERSION.SDK_INT.toString())
    }
}

Then add the header for the device model. Similar to the OS version, the device model is also a runtime value.

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        var version = packageInfo.versionName

        requestBuilder.addHeader(APP_VERSION, version)
        requestBuilder.addHeader(OS_VERSION, Build.VERSION.SDK_INT.toString())
        requestBuilder.addHeader(DEVICE_MODEL, Build.MODEL)
    }
}

Finally, add the header for the platform name, which is Android in this case, and proceed with the request chain

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

    //.. headers

    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val requestBuilder: Request.Builder = request.newBuilder()

        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
        var version = packageInfo.versionName

        requestBuilder.addHeader(APP_VERSION, version)
        requestBuilder.addHeader(OS_VERSION, Build.VERSION.SDK_INT.toString())
        requestBuilder.addHeader(DEVICE_MODEL, Build.MODEL)
        requestBuilder.addHeader(DEVICE_PLATFORM, "android")
        return chain.proceed(requestBuilder.build())
    }
}

Next, head back to the class named OkHttpProvider. Add an instance of AnalyticsInterceptor as an interceptor to the OkHttp client.

val okHttpClient = OkHttpClient.Builder()
    .readTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS)
    .connectTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS)
    .addInterceptor(ApiKeyInterceptor())
    .addInterceptor(AnalyticsInterceptor(context))
    .addInterceptor(loggingInterceptor)
    .build()

Since AnalyticsInterceptor requires an instance of Context, change the method signature of getOkHttpClient to accept a Context as a parameter.

fun getOkHttpClient(context: Context): OkHttpClient {

}

getOkHttpClient is invoked from ApiProvider.getMovieApi(). Open ApiProvider and modify getMovieApi to accept Context as a function parameter.

fun getMovieApi(context: Context): MovieApi {
    if (movieApi == null) {
        movieApi = Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(Gson()))
            .client(OkHttpProvider.getOkHttpClient(context))
            .build()
            .create(MovieApi::class.java)
    }
    return movieApi!!
}

Finally, change the invocations of getMovieApi to pass a Context instance in MovieDetailsFragment and MovieListFragment.

Build and run the app. Open the Logcat tab and you will notice that the Interceptor has added the required analytics data as headers to the API calls.

Congratulations, you’re now a master of OkHttpInterceptors! :]