androidretrofit2

Retrofit : java.lang.IllegalStateException: closed


I am using two kind of interceptor, one is HttpLoggingInterceptor and another one is my custom AuthorizationInterceptor

I am using below updated retrofit version library,

def retrofit_version = "2.7.2"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation 'com.squareup.okhttp3:okhttp:4.4.0'

below is code

private fun makeOkHttpClient(): OkHttpClient {
        val logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
        return OkHttpClient.Builder()
            .addInterceptor(AuthorizationInterceptor(context)) <---- To put Authorization Barrier
            .addInterceptor(logger) <---- To log Http request and response
            .followRedirects(false)
            .connectTimeout(50, TimeUnit.SECONDS)
            .readTimeout(50, TimeUnit.SECONDS)
            .writeTimeout(50, TimeUnit.SECONDS)
            .build()
    }

When I try to execute below code, in file named SynchronizationManager.kt, it gives me an error.

var rulesResourcesServices = RetrofitInstance(context).buildService(RulesResourcesServices::class.java)
val response = rulesResourcesServices.getConfigFile(file).execute() <---In this line I am getting an exception... (which is at SynchronizationManager.kt:185)               

My RulesResourcesServices class is here

After debug I found that when below function called, at that time I am getting an exception

@GET("users/me/configfile")
    fun getConfigFile(@Query("type") type: String): Call<ResponseBody>

I am getting following error

java.lang.IllegalStateException: closed
at okio.RealBufferedSource.read(RealBufferedSource.kt:184)
at okio.ForwardingSource.read(ForwardingSource.kt:29)
at retrofit2.OkHttpCall$ExceptionCatchingResponseBody$1.read(OkHttpCall.java:288)
at okio.RealBufferedSource.readAll(RealBufferedSource.kt:293)
at retrofit2.Utils.buffer(Utils.java:316)<------- ANDROID IS HIGH-LIGHTING
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:103)
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:96)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall.execute(DefaultCallAdapterFactory.java:97)
at android.onetap.SynchronizationManager.downloadFile(SynchronizationManager.kt:185)
at android.base.repository.LoginRepository.downloadConfigFilesAndLocalLogin(LoginRepository.kt:349)
at android.base.repository.LoginRepository.access$downloadConfigFilesAndLocalLogin(LoginRepository.kt:48)
at android.base.repository.LoginRepository$loginTask$2.onSRPLoginComplete(LoginRepository.kt:210)
at android.base.repository.LoginRepository$performSyncLogin$srpLogin$1$1.onSRPLogin(LoginRepository.kt:478)
at android.srp.SRPManager$SRPLoginOperation$execute$1.invokeSuspend(SRPManager.kt:323)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)

Below is screenshot, in that you can see that, I am getting output of file but don't know why it is throwing an exception.

enter image description here

checked Retrofit's Utils class

https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/Utils.java

static ResponseBody buffer(final ResponseBody body) throws IOException {
    Buffer buffer = new Buffer();
    body.source().readAll(buffer); <-This line throws an error.
    return ResponseBody.create(body.contentType(), body.contentLength(), buffer);
  }

Update

Same thing is working fine with enqueue method.

response.enqueue(object : Callback<ResponseBody?> {

override fun onResponse(call: Call<ResponseBody?>, response: retrofit2.Response<ResponseBody?>) { 
 }
})

I have post same issue with Retrofit team, lets see.

https://github.com/square/retrofit/issues/3336


Solution

  • Thanks to JakeWharton (https://github.com/square/retrofit/issues/3336), I can be able to get solution. Actually in my custom interceptor I was reading response by following code

    Response.body().string()
    

    I was doing because above code was helping me to find out that if there is any error than what kind of error it is....

    if it is AUTH_ERROR, I have to generate new token and append it to request header.

    According to retrofit document, if we call any of below method then response will be closed, which means it's not available to consume by the normal Retrofit internals.

    Response.close()
    Response.body().close()
    Response.body().source().close()
    Response.body().charStream().close()
    Response.body().byteStream().close()
    Response.body().bytes()
    Response.body().string()
    

    So to read data, I will use

     response.peekBody(Long.MAX_VALUE).string()
    

    instead of

     response.body().string(), 
    

    so it will not close response.

    below is the final code

     val response = chain.proceed(request)
                val body = response.peekBody(Long.MAX_VALUE).string()//<---- Change
                try {
                    if (response.isSuccessful) {
                        if (body.contains("status")) {
                            val jsonObject = JSONObject(body)
                            val status = jsonObject.optInt("status")
                            Timber.d("Status = $status")
                            if (status != null && status == 0) {
                                val errorCode = jsonObject.getJSONObject("data").optString("error_code")
                                if (errorCode != null) {
                                    addRefreshTokenToRequest(request)
                                    return chain.proceed(request)
                                }
                            }
                        } else {
                            Timber.d("Body is not containing status, might be not valid GSON")
                        }
                    }
                    Timber.d("End")
                    
                } catch (e: Exception) {
                    e.printStackTrace()
                    Timber.d("Error")
                }
                return response