1

I just experience a very difficult problem regarding the newest Retrofit2 and Kotlin.

Client wants to connect to server via retrofit2.

        val client = OkHttpClient.Builder()
            .addInterceptor(object: Interceptor {
                override fun intercept(chain: Interceptor.Chain): Response {
                    try{
                        chain.proceed(chain.request());
                        return chain.proceed(chain.request());
                     }catch(e:Exception){
                        e.printStackTrace()
                      //called
                     }

                    return chain.proceed(chain.request());//leads to crash
                }
            })
            .readTimeout(10, TimeUnit.SECONDS).build()

The retrofitservice is created like this:

        try {//try/catch here is useless -> not called
        retrofit = Retrofit.Builder()
                .baseUrl(Constants.SERVER_BASEURL + ":3000")
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }catch (e:Exception){
            e.printStackTrace()
        }

Any function is called in a coroutine way, similar to this:

@POST("/users/me/logout")
suspend fun logout(@Header("Authorization") token: String): ResponseBody

All this works fine, but as soon as you lose the connection to the server, it is not possible to catch the error, which than automatically leads to an app crash.

For the case of an offline server the exception looks something like this:

java.net.ConnectException: Failed to connect to /XXXXXXX:3000
    at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.kt:270)
    at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:176)
    at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:236)
    at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:109)
    at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:77)
    at okhttp3.internal.connection.Transmitter.newExchange$okhttp(Transmitter.kt:162)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:35)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:71)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
    at com.project.app.Helpers.MasterViewModel$getRetrofitClient$client$1.intercept(MasterViewModel.kt:118)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
    at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:215)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184)
    at okhttp3.RealCall$AsyncCall.run(RealCall.kt:136)
    at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:784)

What I know:

  • The interception of the events, in the first code block shows, that the exception is thrown in

    chain.proceed(chain.request());

    Since the return value of "intercept" is "Response" one would think of just returning an dummy Response. But this also leads to new problems, since its very hard to create such a dummy response and also you lose all request calls. The best would be to just suspend the made requests till the server reconnects.

  • The try,catch block inside the second code block is practically useless. It does nothing to prevent the crash, since the crash is happening inside the request/response pipe, probably inside a coroutine that is launched by retrofit to call the suspend functions.

  • It does not matter if the server timed out or does not exist at all, at the moment when any connection issue happens, the uncatchable exception will be thrown and the app will crash

I guess, this is a general issue that has not be solved yet.

There are similar questions, like this one:

Retrofit and OkHttpClient, catch connection timeout in failure method

but the solution cannot work here, since its not preventing the critical part "chain.proceed(chain.request());" from being called

Any help, besides forking advises, will be appreciated.

1 Answers1

1

I guess you do not need to try/catch your retrofit initialization(nor custom OkhttpClient) but rather each call to the service. Create apiClient and reuse to call the service:

val apiClient = retroft.create(TourService::class.java)
...
//calling API:
try{
    val responseBody = apiClient.logout("some token")
}catch(x: Exception){
    //catch app crash
    when(x){
        is ConnectException -> //TODO: handle connection error       
        is UnknownHostException ->  //TODO: handle no internet connection error
    }
}
Lukas
  • 1,216
  • 12
  • 25