Amira Elsayed Ismail
Amira Elsayed Ismail

Reputation: 9394

how to handle error in okhttp3 without crash

I am using the following method to handle my requests

override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
        .let { originalResponse ->

            Log.i("AMIRA999", "code : " + originalResponse.code())

            when (originalResponse.code()) {

                200 -> {
                    Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
                    originalResponse
                }
                401, 404 -> {


                    Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
                    originalResponse

                    /*return originalResponse.mapToBody(
                        originalResponse.body()?.contentType(),
                        getErrorResponse(originalResponse)
                    )*/
                }
                else -> {
                    Log.i("AMIRA999", "body : " + originalResponse.body().toString())
                    throw BadRequestException()
                }
            }
        }

the method work perfect when the code is 200, but it crash if the code is 404 or 401

what I need to keep returning the json comes from server and does not crash to be able to handle it with error message

how can I do that ?

the crash that I got is the following

retrofit2.HttpException: HTTP 401 UNAUTHORIZED
at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)

Upvotes: 2

Views: 3481

Answers (2)

Enselic
Enselic

Reputation: 4902

You use retrofit2-kotlin-coroutines-adapter and the exception throwing is by design. Any non-2xx HTTP response such as 401 will throw an exception. You can see this for yourself in the library source code

if (response.isSuccessful) {
  deferred.complete(response.body()!!)
} else {
  deferred.completeExceptionally(HttpException(response))
}

But this is not a problem. You can still access the response and your JSON by doing catch (e: HttpException) and then calling val yourJson = e.response()?.body() as? YourJson.

Note that retrofit2-kotlin-coroutines-adapter is deprecated and that you should migrate to Retrofit 2.6.0 or newer. Then you can prefix your Retrofit interface functions with suspend so you can write nice idiomatic Kotlin code.

Upvotes: 2

Jakir Hossain
Jakir Hossain

Reputation: 3930

Retrofit 2 has a different concept of handling "successful" requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as "successful". That means, for these requests the onResponse callback is fired and you need to manually check whether the request is actually successful (status 200-299) or erroneous (status 400-599).

If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.

Example

Error Object

Let’s assume your API sends a JSON error body like this:

{
    statusCode: 409,
    message: "Email address already registered"
}

Note: you can see your JSON error body by printing response.errorBody()

To avoid these bad user experiences, we’re mapping the response body to a Java object, represented by the following class.

class APIError {
  private val statusCode:Int = 0
  private val message:String
  fun status():Int {
    return statusCode
  }
  fun message():String {
    return message
  }
}

Error Handler

object ErrorUtils {
  fun parseError(response:Response<*>):APIError {
    val converter = ServiceGenerator.retrofit()
    .responseBodyConverter(APIError::class.java, arrayOfNulls<Annotation>(0))
    val error:APIError
    try
    {
      error = converter.convert(response.errorBody())
    }
    catch (e:IOException) {
      return APIError()
    }
    return error
  }
}

Error Handler in Action

Now you can handle error in API response using ErrorUtils like the following.

val call = service.me()
call.enqueue(object:Callback<User>() {
  fun onResponse(call:Call<User>, response:Response<User>) {
    if (response.isSuccessful())
    {
      // use response data and do some fancy stuff :)
    }
    else
    {
      // parse the response body …
      val error = ErrorUtils.parseError(response)
      // … and use it to show error information
      // … or just log the issue like we’re doing :)
      Log.d("error message", error.message())
    }
  }
  fun onFailure(call:Call<User>, t:Throwable) {
    // there is more than just a failing request (like: no internet connection)
  }
})

The complete example with a video is here retrofit-2-error-handling.

Upvotes: 0

Related Questions