16

I want to handle error in Retrofit 2.0

Got e.g. code=404 and body=null, but errorBody() contains data in ErrorModel (Boolean status and String info).

This is errorBody().content: [text=\n{"status":false,"info":"Provided email doesn't exist."}].

How can I get this data?

Thank for helping me!

This is my code for Retrofit request:

ResetPasswordApi.Factory.getInstance().resetPassword(loginEditText.getText().toString())
    .enqueue(new Callback<StatusInfoModel>() {
        @Override
        public void onResponse(Call<StatusInfoModel> call, Response<StatusInfoModel> response) {
            if (response.isSuccessful()) {
                showToast(getApplicationContext(), getString(R.string.new_password_sent));
            } else {
                showToast(getApplicationContext(), getString(R.string.email_not_exist));
            }
        }

        @Override
        public void onFailure(Call<StatusInfoModel> call, Throwable t) {
            showToast(getApplicationContext(), "Something went wrong...");
        }
    });
y07k2
  • 1,898
  • 4
  • 20
  • 36

6 Answers6

22

If you want to get data when error response comes (typically a response code except 200) you can do it like that in your onResponse() method:

if (response.code() == 404) {
    Gson gson = new GsonBuilder().create();
    YourErrorPojo pojo = new YourErrorPojo();
    try {
         pojo = gson.fromJson(response.errorBody().string(), YourErrorPojo.class);
         Toast.makeText(context, pojo.getInfo(), Toast.LENGTH_LONG).show();
    } catch (IOException e) { 
      // handle failure at error parse 
  }
}

When generating YourErrorPojo.class do following steps :

  1. Go to Json Schema 2 Pojo

  2. Paste your example Json, and select source type Json , annotation Gson

  3. Your example Json is : {"status":false,"info":"Provided email doesn't exist."}

  4. Click Preview and it will generate your Pojo class for you.

Add this to your build.gradle : compile 'com.google.code.gson:gson:2.7'

I used Gson in this solution but you can get your Json string using: response.errorBody().string()

Jamil Hasnine Tamim
  • 4,389
  • 27
  • 43
Yasin Kaçmaz
  • 6,573
  • 5
  • 40
  • 58
  • I tried this code: `Gson gson = new GsonBuilder().create();` `StatusInfoModel error = new StatusInfoModel();` `try { error = gson.fromJson(response.errorBody().string(), StatusInfoModel.class);` `showToast(getApplicationContext(), error.getInfo());` `} catch (IOException e) { // handle failure to read error }`, but there is an error: `Class must either be declared abstract or implement abstract method`. – y07k2 Jul 11 '16 at 12:15
  • @y07k2 Can you provide screenshot and i will look it ? – Yasin Kaçmaz Jul 11 '16 at 12:19
  • https://s32.postimg.org/eww0vfotx/error.png. When I remove `else` statement error disappears. – y07k2 Jul 11 '16 at 12:22
  • 1
    @y07k2 Ah i see your error, because i missed one "}" , i added it to my answer. Please look edited answer. – Yasin Kaçmaz Jul 11 '16 at 12:30
  • Works perfectly! Thank you! :) P.S. Any idea how can I simplify this (I mean put into method and call it in a different places)? – y07k2 Jul 11 '16 at 12:49
  • @y07k2 if you sure you getting same response when 404 occurred , you can simplfy it. In my api when i get a response except 200 i am getting same response like : `{ "code" : 401, "message": "Authorization has been denied" }` – Yasin Kaçmaz Jul 11 '16 at 12:55
6

Retrofit doesn't see 404 as a failure, so it will enter the onSuccess.

response.isSuccessful() is true if the response code is in the range of 200-300, so it will enter the else there.

if (response.isSuccessful()) {
    showToast(getApplicationContext(), getString(R.string.new_password_sent));
} else {
    // A 404 will go here

    showToast(getApplicationContext(), getString(R.string.email_not_exist));
}

However since your response was not successful, you do not get the response body with .body(), but with errorBody(), errorBody will filled when the request was a success, but response.isSuccessful() returns false (so in case of a status code that is not 200-300).

Tim
  • 41,901
  • 18
  • 127
  • 145
  • Yes, I know that it can be done like this but I don't want to type `String errorMsg` every time, when I can (at least I believe I can) get if from `errorBody()`. – y07k2 Jul 11 '16 at 12:19
  • `errorBody` is for when an error occurs, which does not apply in case of 404. 404 is seen as a success – Tim Jul 11 '16 at 12:23
  • So that is weird. Why I receive this data in `errorBody()`, not in `body()`? – y07k2 Jul 11 '16 at 12:24
  • My bad. `errorBody` contains the response if the response was not successful. So in case there was no error, but the status code was not 200-300. Status code 404 will have the response in errorBody as you said – Tim Jul 11 '16 at 12:27
  • 1
    "response.isSuccessful() is true if the response code is in the range of 200-300" To be precise: http responses beginning with 2XX are successes, 3XX redirections, 4XX client-side errors and 5XX server-side errors. – gonczor Mar 05 '17 at 22:30
3

I'm using this library Retrobomb, you don't have to serialize at that level. it's easy to use and customize. It supports annotation for each error type or error code. If you prefer you can unwrap all errors and handle by your self.

@ErrorMapping(code = 401, errorType = Unauthorized.class)
@PATCH("/v1/widgets/{id}")
  Single<Widget> updateWidget(@Path("id") String id, @Body Widget widget);
schwertfisch
  • 4,549
  • 1
  • 19
  • 32
0

If you want to get data when error response comes (typically a response code except 200) you can do it like that in your onResponse() method:

override fun onResponse(call: Call<LoginData>?, response: Response<LoginData>?) {
    if (response != null) {
        if (response.code() == 200 && response.body() != null) {
            val loginData = response.body()
            if (loginData != null) {
                //Handle success case...
            }
        } else if (response.code() == 401) {
            val converter = ApiClient.getClient()?.responseBodyConverter<ErrorResponseData>(
                ErrorResponseData::class.java,
                arrayOfNulls<Annotation>(0))
            var errorResponse: ErrorResponseData? = null
            errorResponse = converter?.convert(response.errorBody())
            if (errorResponse != null) {
                //Handle Error case..
            }
        }
    }
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
0

For Kotlin:

Just follow this code to convert your errorBody to your response:

if(response.isSuccessful){
     val data = response.body()!!
               
 }else {
    val gson = GsonBuilder().create()

    try {
        var pojo = gson.fromJson(
            response.errorBody()!!.string(),
            CommentResponse::class.java)
            Log.e("ERROR_CHECK","here else is the error$pojo")

     } catch (e: IOException) {
           // handle failure at error parse
  }
}
Jamil Hasnine Tamim
  • 4,389
  • 27
  • 43
0

you can do in the following way

fun parseErrorBody(readText: String?): String {
    if (!readText.isNullOrEmpty()) {
        val result = Gson().fromJson(readText, AppError::class.java)//AppError can be your server error response model
        return result.errors?.get(0)?.message ?: Constants.EMPTY_STR
    }
    return ""
}

and calling code

    if(response.isSuccessful){}//handle success response
else{
    parseErrorBody(response.errorBody()?.charStream()?.readText())
}
Mohd Qasim
  • 896
  • 9
  • 20