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');
CodePudding user response:
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.
CodePudding user response:
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)