Retrofit: Handling error JSON response - android

My web server returns JSON in the following form if successful (200 status code):
{"success":true,"data":{"user_id":"20","username":"Bob"}}
But if something went wrong (for example, if the username and password entered was too short in a form), it returns JSON in the following form (400 status code):
{"success":true,"errors":{"username":["Username too short"],"password":["Incorrect password","Password too short"]}}
How do I handle these two different responses in Retrofit? This is my code so far:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(URL)
.build();
ApiEndpointInterface api = restAdapter.create(ApiEndpointInterface.class);
api.getToken('1', new Callback<DefaultResponse>() {
#Override
public void success(DefaultResponse json, Response response) {
//
}
#Override
public void failure(RetrofitError response) {
//
}
});
How do I handle these two differently structured JSON responses?

You need to do two things.
First, do not return a 400 from the server. as others have stated you actually have a successful response from the server, so you just need to parse the error on your end.
your server can return a flag indicating login success or failure.
After that, you can simply add the error model to your DefaultResponse model. like so
public class DefaultResponse {
public boolean success;
public DataModel data;
public ErrorModel errors;
public static class DataModel {
private String user_id;
private String username;
}
public static class ErrorModel { ... }
}
You will still need to handle instances where your server responds with statuses other than OK, but typically error responses from a server are simple plain text, like "bad request".. You could of course insert json if you wish which you could extract from RetrofitError.getMessage(), but GSON wouldn't parse that for you automatically. I hope this helps!

You should realize that actually both responses you posted are success from the network request perspective.
The failure status is for the request failure (Network connection error, 404, etc.) and not failure you return after trying to login the user in your backend.
You'll need to parse the result in success method and decide weather the internal result is success from your app logic perspective.
You should offcourse also handle failure().

if you get the JSON from your web service, which mean it's success.
so, in your success method, grab the JSON and handle it
for example, from your JSON format
#Override
public void success(DefaultResponse json, Response response) {
try {
JSONObject json = new JSONObject(json);
JSONObject data = json.get("data"); // if no "data" element, it will throw JSONException
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),"username to short",Toast.LENGTH_SHORT).show();
}
}
or you can use GSON to handle your JSON data

Related

Redundancy requests with Retrofit

I need to build in redundancy into my app where if a server is down it will try a backup redundancy server upon failure of the first request.
Aside from doing
Call<LoginResult> loginCall = apiInterface.login(....);
loginCall.enqueue(new Callback<LoginResult>() {
#Override
public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
if(response.isSuccessful){
//do normal stuff
}else{
//try second url
}
}
#Override
public void onFailure(Call<LoginResult> call, Throwable t) {
//Try second url
}
}
I don't see a clean way to do this. Creating another retrofit request inside the error block or non-successful block would add a lot of code complexity.
Is there an easier way to handle this in Retrofit or OkHttp?
I have here an option with OkHttp interceptors. The idea is that if the request fails you replace the url and execute the request again.
The following is an api client to the OpenWeather Api. If you want to try out the example you'll need to sign up and get an api key. It should be free so I hope this is ok.
I'll post here the full code and then walk you through it.
private final static String API_KEY = "<API KEY HERE>";
private static class Weather {
#SerializedName("id")
#Expose
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
private static final String GOOD_HOST = "api.openweathermap.org";
private static final String BAD_ENDPOINT = "https://api.aaaaaaaaaaa.org";
interface WeatherApiClient {
#GET("/data/2.5/weather")
Call<Weather> get(
#Query("q") String query,
#Query("appid") String apiKey);
}
private static class ReplicaServerInterceptor implements Interceptor {
#Override public okhttp3.Response intercept(Chain chain)
throws IOException {
try {
okhttp3.Response response = chain.proceed(chain.request());
return response;
} catch (IOException e) {
// Let's build a new request based on the old one
Request failedRequest = chain.request();
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
}
}
}
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new ReplicaServerInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BAD_ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
WeatherApiClient weatherApiClient =
retrofit.create(WeatherApiClient.class);
weatherApiClient.get("Lisbon,pt", API_KEY)
.enqueue(new Callback<Weather>() {
#Override public void onResponse(
Call<Weather> call,
Response<Weather> response) {
// This might be null sometimes because
// the api is not super reliable, but I didn't
// add code for this
System.out.println(response.body().id);
}
#Override public void onFailure(
Call<Weather> call,
Throwable t) {
t.printStackTrace();
}
});
}
To be able to fake a server failure I prepare retrofit to call a non existent url - BAD_ENDPOINT. This will trigger the catch clause inside the interceptor.
The interceptor itself is obviously the key thing here. It intercepts every call from retrofit and executes the call. If the call throws an error because the server is down, then it will raise an IOException. Here I copy the request being made and change the url.
Changing the url means changing the host:
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
If you just call url(<some url>) in the request builder, everything gets replaced. Query parameters, protocol, etc. This way, we preserve these from the original request.
(OkHttp offers newBuilder methods which copy the data from the current object and let you just edit what you want. Just like kotlin's copy. This is why we can simply change the url and be safe that everything else remains the same)
I then build the new request with the url and execute it:
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
Interceptors work on a chain pattern, that's why calling proceed will call the next interceptor on the chain. In this case we just need to actually make the request.
I didn't bother copying the entire weather resource, so I'm just using the id. I think that's not the main focus of the question
As I said before, this is meant as a proof of concept. As you noticed I'm try-catching the execution of the call, but in your case it might be that the call actually succeeds executing, but the http response is not a 2XX. The okhttp response objects have methods that help you checking if the response was successful namely - isSuccessful(). The idea is the same - Build a new request and carry on if it's not successful.
I didn't bother treating any errors from the replica in this example. They'll just be forwarded to the retrofit client.
As you can see retrofit has no clue where the response is coming from. This might or not be good. Also, the response body needs to be the same from both servers, which I guess it's the case.
Lastly I'm sorry for the awkward okhttp3.Response name spacing there. I was using both Response from retrofit and okhttp and hence had to avoid the name clash.
Versions used for this example: Retrofit 2.3.0 and the okhttp bundled with that

getting a List back from a server using retrofit

I'm new to using Retrofit for networking in Android. So far I have successfully written code that sends a String to a PHP server. The server is configured to take my string, which contains a system ID and a password ("systemid=id&password=password"). It may seem odd to append parameters via a String like this but this is the way I have to do it for this project. Anyway, on the server if the id and password sent to it match certain criteria, it's sends back a list of file locations. So far, I am getting the proper response back from the server. In other words 'response.getMessage()' returns 'OK.' But how can I get the list that the server is trying to send back? Here is my implementation so far.
Interface:
public interface ExampleClient {
#POST("login/fake_name.php")
Call<String> sendStringToServer(#Body String string);
}
Here is my code within the Activity:
private void sendRequestToServer(String str) {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://www.example.com/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
PromoterKiosksClient client = retrofit.create(ExampleClient.class);
Call<List<String>> call = client.sendStringToServer(str);
call.enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
Log.d(TAG, response.toString());
Log.d(TAG, response.message());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
Toast.makeText(AdminActivity.this, "you suck! keep trying", Toast.LENGTH_SHORT).show();
}
});
}
Is there somewhere in onResponse() that I can do this or do I need a totally different implementation? Thanks in advance
You are looking for response.body() ? Do you get a JSON response from the server? If so you can use POJO to convert your response. Would you be able to share your response here? You can use POJO to convert your result and name it Result.java and set that as your return type.
private void sendRequestToServer(String str) {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://www.example.com/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
PromoterKiosksClient client = retrofit.create(ExampleClient.class);
Call<Result> call = client.sendStringToServer(str);
call.enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<Result> call, Response<Result> response) {
Log.d(TAG, response.toString());
Log.d(TAG, response.message());
response.body(); // have your all data
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
Toast.makeText(AdminActivity.this, "you suck! keep trying", Toast.LENGTH_SHORT).show();
}
});
}
Call<User> vs. Call<ResponseBody> vs. Call<Void>
Most endpoints will be declared with a specific return type, like Call. In this case Retrofit will always take the response body and try to convert it to Java objects. Of course, this takes time, memory and processing power.
If you can live without the mapped Java objects, you should choose Call. This makes the raw response payload available to you, but skips the mapping to Java objects. With this option, you still have the chance to analyze the payload (e.g., JSON).
The most efficient way is Call because it not only skips the conversion to Java objects, it also ignores the response body payload. When the response body is very large (e.g., a large JSON or an image), you can save a little bit of extra time and battery consumption by using Call. Of course, this results in the body() method of your response object returning null.
A good example to look at Consuming an API with retrofit and RXJava

Using Volley without Gson

Today I got to know that Retrofit uses gson(or any other convertor) to serialize or deserialize the json response (response that was got using okhttp or any related library).
Now, when I was naive(in some sense still am) and I used to use Volley and at that time I never used Gson or any related library and same for okhttp.But i used to get my response and inflate it successfully on my views.
1. Now does Volley internally do what Retrofit does using Gson and Okhttp?
If not? 2. Then how did i able to get values parsed without using anything?
Below is the sample Codes that i used to write:-
JsonObjectRequest jsonObjectRequest=new JsonObjectRequest(
Request.Method.POST, URL_THUMB, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
JSONArray jsonArray=response.getJSONArray("server_response");
for(int i=0;i<jsonArray.length();i++)
{
JSONObject jsonObject=(JSONObject)jsonArray.get(i);
String id=jsonObject.getString("id");
String artist_name=jsonObject.getString("artist_name");
String img_id=jsonObject.getString("img_id");
listId.add(id);
listArtistName.add(artist_name);
listImgID.add(img_id);
}
recyclerView.setAdapter(comedy_adapter);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}
);
and now just inflate these list values to my views.
Where did I go wrong? (I don't think I was wrong as things went well and code always run fine)
In your example you're parsing the response into JSON arrays and objects manually. Converters such as Gson let you parse the response into a variable of a custom object in a single line.
For example, if I have the following model:
public class Model {
private int id;
private String name;
}
I can parse a string response using the following code:
Model model = gson.fromJson(str, Model.class);
Otherwise, you have to do it manually, like what you're doing at the moment:
JSONObject jsonObject = response.getJSONObject("str");
int id = jsonObject.getInt("id");
String name = jsonObject.getString("name");
Model model = new Model(id, name);
In Retrofit 2 you don't even have to call fromJson - you simple receive the object you expect as an input parameter in onResponse. It's very useful when dealing with more complex models.

rx java retrofit 2 error handling

I'm using retrofit 2 along with rx java
Situation:
the app sends some request, then i get the response in json-format that is automatically converted to User dto, then in onNext method of rx java i receive the list of Users. What if i get some message from server like this: {"error":"can't get the list of users"}
how to handle this situation with retrofit 2 and rx?
Subscription subscriptionBranches = model.getRepoBranches(owner, name)
.map(branchesMapper)
.subscribe(new Observer<List<Branch>>() {
#Override
public void onCompleted() {
;
}
#Override
public void onError(Throwable e) {
if (e instanceof retrofit.HttpException) {
HttpException exception = (HttpException) e;
}
showError(e);
}
#Override
public void onNext(List<Branch> list) {
branchList = list;
view.showBranches(list);
}
});
addSubscription(subscriptionBranches);
.....
#Override
public Observable<List<RepositoryDTO>> getRepoList(String name) {
return apiInterface
.getRepositories(name)
.compose(applySchedulers());
}
Depending on the server response you might or might not get into your onError function. If the server returns a non-2XX http status code you'll get into the onError method. If on the other hand you get a 2XX http status code you'll enter onNext.
I'm assuming you can deal with the onNext bit and I'll explain how you can do it in the onError. It's important to realise that there are many ways of doing this and this is just an example that uses okhttp 3 and retrofit 2 beta4.
So retrofit2 says that every non-2XX http responses are HttpExceptions when using rxjava. This you already have it there in your code:
if (e instanceof retrofit.HttpException) {
HttpException exception = (HttpException) e;
}
Now what you want to do is get the body of the response. This you can achieve by calling Response response = exception.response() in the HttpException you have there. With the response, getting the error body is quite straight forward. You just call response.errorBody(). You can then convert the body to a java object or just access it as a string.
Since you have a json error body as an example, here's how you can convert the response body to a java object:
new GsonConverterFactory().responseBodyConverter(type,
new Annotation[0]).convert(response.errorBody());
where type is the class of the java object that represents the error.
So putting it all together, on your onError method you could write something like:
if (e instanceof retrofit.HttpException) {
HttpException exception = (HttpException) e;
Response response = exception.response();
Converter<ResponseBody, MyError> converter = new GsonConverterFactory()
.responseBodyConverter(MyError.class, Annotation[0]);
MyError error = converter.convert(response.errorBody());
}
MyError is a model that represents the error json you have in your question.
I believe in the case you mentioned you will just enter into your onError handling, because retrofit will fail to deserialize your response, as it's not formatted as a List. You could potentially handle your case through that based off of the exception type.
If you can't alter the api to return consistent response types, you will have to look into using TypedInput, and possibly a converter.
Additionally, while it may not be completely relevant/overkill to the situation at hand, TypeAdapters bear mentioning. They'll let you determine how retrofit deserializes gson on a per class basis.
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyClass.class, new MyAdapter())
.create();
RestAdapter adapter = new RestAdapter.Builder()
.setConverter(new GsonConverter(gson))
.build();
I had the same situation and the way I could get the json from the server when an error occurs was something like this:
retrofit2.Response<TokenRefresh> r = call.execute();
String errorMessage = "";
try {
errorMessage = r.errorBody().string();
} catch (IOException e) {
e.printStackTrace();
}
Timber.d("errorMessage: " + errorMessage);

Send JSON in Post with robospice google http client

I have a problem with creating post requests and send json with Robospice google http java client. My problem is, that the server receives an empty request data. (Nothing in postData)
#Override
public AjaxResult loadDataFromNetwork() throws Exception {
JsonHttpContent jsonHttpContent = new JsonHttpContent(new JacksonFactory(), jsonObject);
//ByteArrayContent.fromString("application/json", jsonObject.toString())
HttpRequest request = getHttpRequestFactory().buildPostRequest(
new GenericUrl(baseUrl),
jsonHttpContent);
request.getHeaders().setContentType("application/json");
request.setParser(new JacksonFactory().createJsonObjectParser());
request.setContent(jsonHttpContent);
HttpResponse httpResponse = request.execute();
AjaxResult result = httpResponse.parseAs(getResultType());
return result;
}
Thanks in advance!
You can do something like this :
public class SignIn_Request extends GoogleHttpClientSpiceRequest<Login> {
private String apiUrl;
private JSONObject mJsonObject;
public SignIn_Request(JSONObject mJsonObject) {
super(Login.class);
this.apiUrl = AppConstants.GLOBAL_API_BASE_ADDRESS + AppConstants.API_SIGN_IN;
this.mJsonObject = mJsonObject;
}
#Override
public Login loadDataFromNetwork() throws IOException {
Ln.d("Call web service " + apiUrl);
HttpRequest request = getHttpRequestFactory()//
.buildPostRequest(new GenericUrl(apiUrl), ByteArrayContent.fromString("application/json", mJsonObject.toString()));
request.setParser(new JacksonFactory().createJsonObjectParser());
return request.execute().parseAs(getResultType());
}
}
Convert your JSON into byte array and include it in your post request.
I've been hunting around for a similar solution myself and I found a decent explanation of how Google want you to format the content.
I made POJO class and just added some getters and setters and used that for the data and it seemed to work for me.
google-http-java-client json update existing object

Categories

Resources