I'm using a custom Interceptor
along with Retrofit client in my Android app, that throws an Exception under some specific circumstances. I'm trying to make it work using Kotlin coroutines.
The problem is that I'm unable to handle the before mentioned error, since in the moment the exception is thrown from within the Interceptor instance, it crashes the whole app instead of being caught in the coroutine's try/catch
statement. While I was using the Rx implementation, the exception was flawlessly propagated to the onError
callback where I was able to handle it the way I needed.
I guess this is somehow related to the underlying threads that are being used for the network call, please see the logs below from the place where the call is made, from the interceptor just before throwing the exception, and the stacktrace:
2019-11-04 17:17:34.515 29549-29729/com.app W/TAG: Running thread: DefaultDispatcher-worker-1
2019-11-04 17:17:45.911 29549-29834/com.app W/TAG: Interceptor thread: OkHttp https://some.endpoint.com/...
2019-11-04 17:17:45.917 29549-29834/com.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.app, PID: 29549
com.app.IllegalStateException: Passed refresh token can\'t be used for refreshing the token.
at com.app.net.AuthInterceptor.intercept(AuthInterceptor.kt:33)
What am I supposed to do in order to be able to catch and handle this exception from the Interceptor correctly? Am I missing something?
You should subclass IOException
and use that to send information from your interceptors to your calling code.
We consider other exceptions like IllegalStateException
to be application crashes and do not send them over thread boundaries because we don’t want to burden most callers with catching them.
You may catch the exception in your custom Interceptor
and return an empty response with some specific message
and code
. I have implemented a custom Interceptor
to handle the situation like when you do not have or slow internet connection etc... Actually coroutine's suspend functions throws exception when dealing with network calls. In my experience, you can follow 2 approaches. 1. wrap your all network call in try...catch
or 2. create a custom Interceptor
and handle exceptions there and return some specific response.
Approach 1:
try {
webservice.login(username, password)
} catch (e: Exception) {
//...
}
Approach 2:
Create a custom Interceptor
and handle exception there.
class LoggingInterceptor : Interceptor {
@Throws(Exception::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
try {
val response = chain.proceed(request)
val bodyString = response.body()!!.string()
return response.newBuilder()
.body(ResponseBody.create(response.body()?.contentType(), bodyString))
.build()
} catch (e: Exception) {
e.printStackTrace()
var msg = ""
when (e) {
is SocketTimeoutException -> {
msg = "Timeout - Please check your internet connection"
}
is UnknownHostException -> {
msg = "Unable to make a connection. Please check your internet"
}
is ConnectionShutdownException -> {
msg = "Connection shutdown. Please check your internet"
}
is IOException -> {
msg = "Server is unreachable, please try again later."
}
is IllegalStateException -> {
msg = "${e.message}"
}
else -> {
msg = "${e.message}"
}
}
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(999)
.message(msg)
.body(ResponseBody.create(null, "{${e}}")).build()
}
}
}
I have created gist for complete implementation of LoggingInterceptor
with print logs of request and response. LoggingInterceptor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With