Retrofit: Get original response body when request fails - android

I am using Retrofit (1.6.1) with Robospice (1.4.14) to get data from some services (response should be in JSON).
In some cases, I might receive a HTML error page instead of a JSON response. The server returns a 200 status code, and I can not change that. In such cases, RoboSpice will call the onRequestFailure(SpiceException) method.
There, I am able to get the original RetrofitError excpetion, but the body is null. This is how I get it:
if (spiceException.getCause() instanceof RetrofitError) {
RetrofitError error = (RetrofitError) spiceException.getCause();
error.getBody(); // returns null
}
After investigating the source code of Retrofit, I found out that the body is replaced with null if the conversion fails (which is the case here, as Retrofit expect JSON and receives HTML).
The following line in RestAdapter is the source of my issue:
response = Utils.replaceResponseBody(response, null);
Is there a way to not set the body to null? In an other SO question, I found that if the server returns 4xx, the body is kept, but I can not change that.

You should probably create a retrofitted method that will just return a retrofit.client.Response and manually call conversion on it if the response body is in the necessary format.
Your Retrofit interface:
...
#GET("/foo/bar")
Response fooBarMethod(Object foo, Object bar);
...
Your RoboSpice request:
...
#Override
public final FooBar loadDataFromNetwork() throws Exception {
Response r = getService().fooBarMethod(foo, bar);
if (isBodyInHtmlFormat()) {
// cool stuff
throw new ResponseIsHtmlException();
} else {
// it is wise to make sure that it is
// exactly the same converter you are passing to
// your RetrofitSpiceService
Converter converter = createGsonConverter();
return (FooBar) converter.fromBody(response.getBody(), FooBar.class);
}
}

Related

Unable to get json string as response in Retrofit 2.0 android

This is not a duplicate question.
I am using Retrofit 2.0 and json for network tasks.
Also I am not using GSON to parse json instead I am using simple JsonObject and JsonArray to get model objects from json string.
Firstly guide me which retrofit converter must be used for above scenario.
Secondly, I am not able to get json string as response string.
I tried two approaches -
Approach 1 - I used Call< Void >. In this case the response.body() returns null though status code is 200.
Approach 2 - I used Call< ResponseBody >. In this case call.enqueue methods call 'on failure method' instead of 'onSuccess' and also the response body is null.
The status code is 200 in this case also.
Please suggest how to get the json string as response from retrofit 2.0.
you need to use JsonObject instead of Void or ResponseBody. Your code should be
Call<JsonObject> getCall = request.getDataCall();
getCall.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
}
});
Note : make sure you are using com.google.gson.JsonObject

Retrofit, top level json object changes name

I am using Retrofit to make an api call. The top level object is named depending on the query parameters of the request. For example, a request like this:
api.somewebsite.com/1.0/mix_info?mix_id=69
returns a response like this:
{"69":{
"mix_id":"69",
"mix_title":"A Title",
"mix_file":"https:example.com/mp3",
"mix_genres":"House,Pop",
"mix_dj_id":"57",
"number_votes":"390",
"station":"1"
}
}
Heres a screenie as well that kinda shows the format:
http://prnt.sc/axltcd
Basically, depending on the number passed into the url mix_id= query becomes the name of the top level json object returned in the response.
I was able to hack together something that kinda works:
I have a model Response class which has a member variable that is the top level object, and I use Retrofit's #SerializedName("69") to explicity set the name. This, of course, will only work for a request with the id of 69, otherwise the response returns null object.
It looks like this:
public class Response {
#SerializedName("69")
private _69 _69;
}
Anyway, I'm looking for a way to properly handle these oddly formatted responses. How would this be done?
You can receive your response as JsonElemen, convert it to JsonObject and get element by global variable which you was using for send request.
Response:
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
JsonElement jsonElement = response.body();
JsonObject jObj= jsonElement.getAsJsonObject();
JsonObject number= jObj.get(CONSTANT_WHICH_YOU_USED_FOR_REQEST).getAsJsonObject();
}
Feel free to ask me if any.

Retrofit 2.0 : onResponse called with null when HTTP Code is 404

When REST Api returns 404, onResponse called with NULL Response. We have user search api and if the user is not found on the server our REST api returns 404 with error response in body with more details.
As Retrofit 2.0 returns a null response body, it's hard for us to show the correct error to the user.
Do we have an alternate solution to get a response during 404?
Response:
{"responseStatus":{"code":"00297","severity":"ERROR","message":"Profile not found..","info":"","status":404}}
HTTP Status Code:
404
Thanks
Retrofit 2.0 shouldn't be returning a null response. If you are making an async callback and want the handle the error it should look something like this
// Create a very simple REST adapter which points the GitHub API.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square", "retrofit");
call.enqueue(new Callback<ArrayList<Item>>() {
#Override
public void onResponse(Call<ArrayList<Item>> call, Response<ArrayList<Item>> response) {
if(response.isSuccessful()) {
// do something
}
else {
Log.e("Error Code", String.valueOf(response.code()));
Log.e("Error Body", response.errorBody().toString());
//display the appropriate message...
}
}
#Override
public void onFailure(Call<ArrayList<Item>> call, Throwable t) {
}
});
Some of that code was copied from Retrofit Samples. If you are getting a null response on a 404 something else is going wrong.

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);

Okhttp Put method returning 'Method Not Allowed'

I am attempting to call a put method on my server using OkHttp from an Android application.
This is the api method signature:
public void Put(int userId, string regId)
{
}
This is the Android code to call the above method:
private boolean SendGCMRegIdToServer(String registrationId, Integer userId) throws IOException {
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host(serverApiHost)
.addPathSegment("AppDashboard")
.addPathSegment("api")
.addPathSegment("GCM/")
.build();
MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
String json = "{'userId':" + userId + ","
+ "'regId':'" + registrationId + "'"
+ "}";
RequestBody requestBody = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.put(requestBody)
.build();
//this should post the data to my server
Response response = client.newCall(request).execute();
if(response.code() == 400)
return false;
return true;
}
Now the problem is I am getting the error code 405 in the response saying Method not allowed, but I cannot see where the problem is because I can successfully call the method using Postman on the server itself as below:
http://localhost/AppDashboard/api/GCM?userId=5&regId=123
I'm thinking it may have something to do with an integer or string being passed incorrectly in the JSON string, but cannot see why this isn't working.
i had the same problem and server was returning 405 . after some search i realized that is a configuration problem on IIS that does not let put requests. so there is no problem in android code and you should config your server to let this kind of requests.
see this , this and this
Ok thanks for replies guys but seems I was getting a little confused between the two methods I was using to pass the params to my API.
Here's what I did:
changed the signature of the method to post with a param [FromBody] as a Model (only supports one paramater)...
public void Post([FromBody]UserGcmRegIdModel model)
{
}
I was then able to change my method call to the following using a nicer JSONBuilder and using .post in the request builder rather than .put
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("UserId", userId);
jsonObject.put("RegId", registrationId);
} catch (JSONException e) {
e.printStackTrace();
}
String json = jsonObject.toString();
RequestBody requestBody = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
I still don't know if there is a problem with put() methods on IIS but using a post in my case was absolutely fine so I'm going with that...
I see two different approaches in your REST api calls. In the one of OkHttp you send a PUT method with a JSON object serialized, and in POSTMAN you send a PUT (although I guess you do a GET) request with the parameters within the URL, I mean not in JSON body structure.
Anyway, HTTP 405 is telling you that your backend does not support the PUT method, and probably it's expecting a POST method with the "X-HTTP-Method-Override:PUT" HTTP header since POST is more standard method in REST than PUT.
What would I do is check your POSTMAN request carefully and adjust the one of Android to be the same method, parameters and headers, not more.
Answer Update (as question has been updated)
Of course there is a problem with that verb, as I said above IIS handles only the standard methods and PUT is not one of those. You have three choices:
Change your PUT to POST.
Use POST with X-HTTP-Method-Override to PUT. (reference)
Modify IIS config to support non standard REST methods. I
personally wouldn't suggest the 3rd one, since it's attached to the
backend config (e.g. imagine you change IIS to NancyFX).

Categories

Resources