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/.
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! :]