Usually, when using Retrofit 2, we have two callback listeners: onResponse
and onFailure
If onResponse
is called, it doesn't always mean that we get the success condition. Usually a response is considered success if the status scode is 2xx and Retrofit has already provides isSuccessful()
method. The problem is how to parse the response body which most likely has different format. In this tutorial, I'm going to show you how to parse custom JSON response body in Retrofit 2.
Preparation
First, we create RetrofitClientInstance
class for creating new instance of Retrofit client and the MyService
interface which defines the endpoint we're going to call.
RetrofitClientInstance.java
public class RetrofitClientInstance {
private static final String BASE_URL = "http://159.89.185.115:3500";
public static Retrofit getRetrofitInstance() {
return new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
MyService.java
public interface MyServie {
@GET("/api/items")
Call<List> getItems();
}
Let's say we have an endpoint /api/items
which in normal case, it returns the list of Item
objects.
Item.java
public class Item {
private String id;
private String name;
public Item(String id, String name) {
this.id = id;
this.name = name;
}
/* Getter and Setter here */
}
Below is the standard way to call the endpoint.
Example.java
MyService myService = RetrofitClientInstance
.getRetrofitInstance()
.create(MyService.class);
Call<List<Item>> call = retailService.getItems();
call.enqueue(new Callback<List
<Item>>() {
@Override
public void onResponse(final Call<List
<Item>> call, final Response<List<Item>> response) {
List<Item> items = response.body();
Toast.makeText(getContext(), "Success").show();
}
@Override
public void onFailure(final Call call, final Throwable t) {
Toast.makeText(getContext(), "Failed").show();
}
});
Parsing Error Response
The problem is how to parse the response if it's not success. The idea is very simple. We need to create custom util for parsing the error. For example, we know that the server will return a JSON object with the following structure when an error occurs.
{
"success": false,
"errors": [
"Error message 1",
"Error message 2"
]
}
We need to create a util for parsing the error. It returns an Object containing parsed error body. First, we define the class which represents the parsed error.
APIError.java
public class APIError {
private boolean success;
private ArrayList messages;
public static class Builder {
public Builder() {}
public Builder success(final boolean success) {
this.success = success;
return this;
}
public Builder messages(final ArrayList messages) {
this.messages = messages;
return this;
}
public Builder defaultError() {
this.messages.add("Something error");
return this;
}
public APIError build() { return new APIError(this); }
}
private APIError(final Builder builder) {
success = builder.successs;
messages = builder.messages;
}
}
And here's the util for parsing the response body. If you have different response body format, you can adjust it to suit your case.
ErrorUtils.java
public class ErrorUtils {
public static APIError parseError(final Response<?> response) {
JSONObject bodyObj = null;
boolean success;
ArrayList messages = new ArrayList<>();
try {
String errorBody = response.errorBody().string();
if (errorBody != null) {
bodyObj = new JSONObject(errorBody);
success = bodyObj.getBoolean("success");
JSONArray errors = bodyObj.getJSONArray("errors");
for (int i = 0; i < errors.length(); i++) {
messages.add(errors.get(i));
}
} else {
success = false;
messages.add("Unable to parse error");
}
} catch (Exception e) {
e.printStackTrace();
success = false;
messages.add("Unable to parse error");
}
return new APIError.Builder()
.success(false)
.messages(messages)
.build();
}
}
Finally, change the onResponse
and onFailure
methods. If response.isSuccessful()
is false, we use the error parser. In addition, if the code go through onFailure
which most likely we're even unable to get the error response body, just return the default error.
Example.java
MyService myService = RetrofitClientInstance
.getRetrofitInstance()
.create(MyService.class);
Call<List<Item>> call = retailService.getItems();
call.enqueue(new Callback<List<Item>>() {
@Override
public void onResponse(final Call<List<Item>> call, final Response<List<Item>> response) {
if (response.isSuccessful()) {
List<Item> items = response.body();
Toast.makeText(getContext(), "Success").show();
} else {
apiError = ErrorUtils.parseError(response);
Toast.makeText(getContext(), R.string.cashier_create_failed,
Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(final Call<Cashier> call, final Throwable t) {
apiError = new APIError.Builder().defaultError().build();
Toast.makeText(getContext(), "failed").show();
}
});
That's how to parse error body in Retrofit 2. If you also need to define custom GSON converter factory, read this tutorial.