1

I am using okhttp 4.9.0 to make API requests, but seems that can't get the response body as JSONOBJECT. This is my code:

client.newCall(request).enqueue(object : Callback {

            override fun onFailure(call: Call, e: IOException) {
                Log.d("respuesta","fallida")
            }
            override fun onResponse(call: Call, response: Response)
            {
                val codigoRespuesta: Int = response.code
                if(codigoRespuesta == 401) //Quiere decir que hubo un error al autentificar
                {
                    pantalla.cerrarSesion("Auth error")
                }
                Log.d("Response", response.body!!.string())
                val respuesta: JSONObject = JSONObject(response.body?.string())

                pantalla.procesarResultado(respuesta)
            }
        })

I get the following error:

E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher

    Process: com.ximhai.vekcheckin, PID: 22096
    java.lang.IllegalStateException: closed
        at okio.RealBufferedSource.select(RealBufferedSource.kt:218)
        at okhttp3.internal.Util.readBomAsCharset(Util.kt:258)
        at okhttp3.ResponseBody.string(ResponseBody.kt:187)
        at com.ximhai.vekcheckin.apiRetro$revisarBoleto$1.onResponse(apiRetro.kt:79)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

On the debug "Log.d("Response", response.body!!.string())" I get:

D/Response: {"resultado":0,"error":"Ticket not found"}

Which means that the API call is actually responding a JSON string.

Now, if I copy the response string and hard code it in the JSON Object like:

val respuesta: JSONObject = JSONObject("{\"resultado\":0,\"error\":\"Ticket not found\"}")

The code works perfectly. (backslashes added automatically by Android Studio when pasting the string).

I have tried:

val respuesta: JSONObject = JSONObject(response.body!!.string()) -> Same result

val respuesta: JSONObject = JSONObject(response.body?.toString()) -> Same result

val respuesta: JSONObject = JSONObject(response.body!!.toString()) -> Same result

val respuesta: JSONObject = JSONObject(response.body().string()) -> Build error: Using 'body(): ResponseBody?' is an error. moved to val

As a piece of extra information: The API is supposed to be responding with Header: Content-type -> application/json

    $newResponse = $response->withHeader('Content-type', 'application/json');
A S M Sayem
  • 2,010
  • 2
  • 21
  • 28
Vianick Oliveri
  • 167
  • 1
  • 9
  • 1
    `response.body.string()` can be consumed only once, it becomes exhausted after first call. Second thing is that body is not null-safe meaning you should either check for null value or use null safe (?) operator but class `JSONObject` requires non-null json string in the constructor parameter. So IMO, remove log and then try making response null safe by storing it in local variable and then use that local variable on json object. – Jeel Vankhede May 04 '22 at 07:13

2 Answers2

2

Could it be that when logging the response you're exhausting the response object?

i.e. - comment out Log.d("Response", response.body!!.string()) and see if anything changes.

Yarin_007
  • 1,449
  • 1
  • 10
  • 17
  • Thanks for your answer but is still the same. I actually added the Log.d() line after detecting the issue to see what was the value of the response body and try to figure out why is this happening. – Vianick Oliveri May 04 '22 at 03:55
  • Hmm. Odd. other ideas - The response header 'Content-type' should be 'Content-Type'. and try exactly [this](https://stackoverflow.com/a/34471060/4935162) syntax. and try with a [different](https://jsonplaceholder.typicode.com/todos/1) endpoint. – Yarin_007 May 04 '22 at 17:37
  • That was a great catch, unfortunately, same result :( – Vianick Oliveri May 06 '22 at 19:36
  • I think is a combination of both of your answers, because after changing to "Content-Type" I commented the Log.d line and now it's working. THANKS A LOT!!! – Vianick Oliveri May 06 '22 at 19:51
  • You are most welcome :) – Yarin_007 May 06 '22 at 21:18
0

Why don't you use Retrofit with okhttp3? With retrofit is so much easier to debug. See Why to use Retrofit?

First, you should create an interface for your API:

import okhttp3.ResponseBody
import retrofit2.http.*

interface myAPI{

// some examples

// request: url/api/authenticate?id=42
@GET("authenticate")
    suspend fun connexion(@Query("id") idUser: Int,): ObjectResponse 

// request: url/api/preparation/uploadImage?id=1
@POST("preparation/uploadImage")
    suspend fun uploadImage(@Query("id") idPrep: Int,
                            @Body image: String,): ResponseBody


}

Then you create a remote data provider:

class RemoteDataProvider(context: Context) {
   private var URL = "www.yourapi.com" 

   private val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
        this.level = HttpLoggingInterceptor.Level.BODY
    }

    private val client: OkHttpClient = OkHttpClient.Builder().apply {
        this.addInterceptor(interceptor)
    }.build()

    // retrofit creation --> main-safe APIs
    private val retrofit = Retrofit.Builder()
            .baseUrl(URL)
            .client(client)
            .addConverterFactory(ScalarsConverterFactory.create()) // string
            .addConverterFactory(GsonConverterFactory.create()) //Converters can be added to support other types in body
            .build()

    private val service: yourAPI = retrofit.create(yourAPI::class.java)

    // connexion
    suspend fun connexion(id: Int,): Object{
        return service.connexion(id).myObject // call to API interface
    }

    // a [BufferedSource] which contains the [String] of the [ResponseBody].
    suspend fun uploadImage(id: Int, image: String,): BufferedSource{
        return service.uploadImage(id, image).source()
    }
}

Then, you create your data repository:

class DataRepository(private val remoteDataSource: RemoteDataProvider){

    companion object {
        private var TAG = "DataRepository"

        fun newInstance(context: Context): DataRepository {
            return DataRepository(
                    remoteDataSource = RemoteDataProvider(context)
            )
        }
    }

    // connexion
    suspend fun connexion(id: Int,): MyObject{
        return remoteDataSource.connexion(id) // object json {user: ...}
    }

    suspend fun uploadImage(id: Int, image: String,): String{
        return remoteDataSource.uploadImage(idPrep, image).toString() // {"response": ...}
    }

Finally you make the call to the data repository (from your ViewModel preferably):

private val dataRepository by lazy { DataRepository.newInstance(application) }
dataRepository.connexion(id)
ladytoky0
  • 588
  • 6
  • 16