Using Retrofit Interceptors to check network connection in Android and testing it
Tags: android, kotlin, mobile, tristan
Published: 2024-01-23 15:28:11

### Table of contents
1. [Too long didn't read. Give me the code!!!](#tldr)
2. [The problem I am trying to solve](#solve)
3. [What is a Interceptor](#interceptor)
4. [How to monitor the Network](#monitor)
5. [Creating the Interceptor](#create)
6. [Adding Interceptor to Retrofit](#adding)
7. [Catching the exception](#catch)
8. [Testing code](#test)

### My app on the Google play store
- [The app](https://play.google.com/store/apps/details?id=elliott.software.clicker)


###GitHub code
- [Entire GitHub application code](https://github.com/thePlebDev/Clicker)

### Too long didn't read (TLDR) <a name="tldr"></a>
- [My GitHub code for finished NetworkMonitorInterceptor](https://github.com/thePlebDev/Clicker/blob/f7007d3e21f7acee4b21dc8ae0604edee62b0cbe/app/src/main/java/com/example/clicker/network/interceptors/NetworkMonitorInterceptor.kt#L16)



### The problem I am trying to solve <a name="solve"></a>
- Currently I have no way to differentiate between types of network failures in my application. Being able to differentiate is  a must. For example, our application should behave differently to  a 401 response vs a complete absence of any network.
- In this tutorial we are only going to talk about notifying the user if they make a request and no network is available at all. Not the most exciting, but it is a good starting point to get a better understanding of [Interceptors](https://square.github.io/okhttp/features/interceptors/)

### Moving forward
- from this point on, I will assume, you have a basic understanding of [Retrofit](https://square.github.io/retrofit/). To get the most out of this tutorial I would actually suggest you have a retrofit client already implemented in your application.


### What is a Interceptor <a name="interceptor"></a>
- As the [documentation](https://square.github.io/okhttp/features/interceptors/) states: `Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls.`
- There are two types of interceptors: [Application Interceptors](https://square.github.io/okhttp/features/interceptors/#application-interceptors) and [Network Interceptors](https://square.github.io/okhttp/features/interceptors/#network-interceptors). We are going to be using a `Application Interceptor` simply because, as the documentation states: `Are always invoked once`. So on every request our application interceptor will be able to check the network's availability.

### How to monitor the Network <a name="monitor"></a>
- To do the actual checking of the network we are going to rely on the [ConnectivityManager](https://developer.android.com/training/monitoring-device-state/connectivity-status-type) object. This will give us the necessary API to check the network before sending a request.
- Our full code is going to look like this:
```
/**
 * LiveNetworkMonitor is a class meant to check the application's network connectivity
 *
 * @param context a [Context] object. This is what is used to get the system's connectivity manager
 * */
class LiveNetworkMonitor @Inject constructor(
    private val context: Context
):NetworkMonitor {
    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    override fun isConnected(): Boolean {
        val network =connectivityManager.activeNetwork
        return network != null
    }
}

```
- Notice how I am using [Hilt](https://dagger.dev/hilt/) to inject the Context object. Also, I want to draw your attention to the `.activeNetwork` method call. Now I am not 100% sure if this is the proper way to check the networks availability. However, it does work and here is the documentation to support my case for using it, [Full documentation](https://developer.android.com/reference/kotlin/android/net/ConnectivityManager#getactivenetwork),: `This will return null when there is no default network, or when the default network is blocked.`

- If you are curious about the `NetworkMonitor,` it is just a simple interface I have created to adhere to the principle of `program to an interface not an implementation` and to make testing easier:
```
/**
 * NetworkMonitor is a interface meant to act as the API to all network monitoring related issues
 *
 * @param isConnected a function used to determine if the application is connected to a network or not
 * */
interface NetworkMonitor {
    fun isConnected():Boolean
}
- This might seem like a bunch of unnecessary Object oriented coding techniques. However, this will make testing 100000% easier. So please use the interface

```
### Creating the Interceptor <a name="create"></a>
- To create the an Interceptor we need to extend the Interceptor interface. Which will allow us to override the `intercept()` function and fill it with our network monitoring logic. So our full interceptor will look like this:
```
/**
 * NetworkMonitorInterceptor is a [application-interceptor](https://square.github.io/okhttp/features/interceptors/#application-interceptors)
 * meant to first check the status of the Network before sending the request
 *
 * @param liveNetworkMonitor a [NetworkMonitor] implementation that handles all of the actual network logic checking
 * */
class NetworkMonitorInterceptor @Inject constructor(
    private val liveNetworkMonitor:NetworkMonitor
): Interceptor {


    @Throws(NoNetworkException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()

        if(liveNetworkMonitor.isConnected()){
            return chain.proceed(request)
        }else{

            throw NoNetworkException("Network Error")
        }

    }
}

```
- I want to call your attention to `chain.request()`, because as the documentation states: `A call to chain.proceed(request) is a critical part of each interceptor’s implementation. This simple-looking method is where all the HTTP work happens, producing a response to satisfy the request. `
- Also, you may notice the `NoNetworkException`. This is a custom exception that we will use to identify when there is no network. It's just a class that extends the IOException:
```
 class NoNetworkException(message:String): IOException(message)
```
- We have to extend IOException because that is the only type of exception allowed inside of the `intercept()` function.
- Now we can move on to adding this interceptor to our Retrofit client

### Adding Interceptor to Retrofit <a name="adding"></a>
- To creating and add the interceptor to Retrofit is actually very straight forward. All we have to do is create a `OkHttpClient`(What Retrofit uses under the hood) and call `addInterceptor()`. Here is how I created and added the Interceptor in a [Hilt module](https://dagger.dev/hilt/modules):
```
@Module
@InstallIn(SingletonComponent::class)
object SingletonModule {
   @Provides
    fun provideNetworkMonitor(
        @ApplicationContext appContext: Context
    ): NetworkMonitor{
        return LiveNetworkMonitor(appContext)
    }

    @Singleton //scope binding
    @Provides
    fun providesTwitchClient(
        liveNetworkMonitor: NetworkMonitor
    ): TwitchClient {
         val monitorClient = OkHttpClient.Builder()     
    .addInterceptor(NetworkMonitorInterceptor(liveNetworkMonitor))
            .build()
        return Retrofit.Builder()
            .baseUrl("https://api.twitch.tv/helix/")
            .addConverterFactory(GsonConverterFactory.create())
            .client(monitorClient)
            .build().create(TwitchClient::class.java)
    }
}

```
- Notice that we simply call `.client(monitorClient)` which will add out custom Interceptor.

### Catching the exception <a name="catch"></a>
- In the Network layer of my application I have this code that reaches out the the Twitch servers and awaits a response:
```
class TwitchRepoImpl @Inject constructor(
    private val twitchClient: TwitchClient
) : TwitchRepo  {


    override suspend fun getFollowedLiveStreams(
        authorizationToken: String,
        clientId: String,
        userId: String
    ): Flow<Response<List<StreamInfo>>> = flow {
        emit(Response.Loading)

        val response = twitchClient.getFollowedStreams(
            authorization = "Bearer $authorizationToken",
            clientId = clientId,
            userId = userId
        )

        val emptyBody = FollowedLiveStreams(listOf<StreamData>())
        val body = response.body() ?: emptyBody
        val exported = body.data.map { it.toStreamInfo() }

        if (response.isSuccessful) {
            emit(Response.Success(exported))
        } else {
            emit(Response.Failure(Exception("Error!, code: {${response.code()}}")))
        }
    }.catch { cause ->
        when (cause) {
        is NoNetworkException -> {

            emit(Response.Failure(Exception("Network error, please try again later")))
        }

        else -> {
            emit(Response.Failure(Exception("Error! Please try again")))
        }
    }
    }
}

```
- You can look at the `catch{}` on our flow. This will catch our `NoNetworkException` when it is thrown by our interceptor.

### Testing code <a name="test"></a>
- I ran out of time writing this blog post, meaning I can't go into detail about testing. So we will have to settle with the GitHub link to the testing code: [HERE](https://github.com/thePlebDev/Clicker/blob/f7007d3e21f7acee4b21dc8ae0604edee62b0cbe/app/src/test/java/com/example/clicker/network/repository/TwitchAuthenticationImplTest.kt#L34)

### Conclusion 
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).