Retrofit / OkHttp3 400 Error Body Empty - android

I am using Retrofit 2 in my Android project. When I hit an API endpoint using a GET method and it returns a 400 level error I can see the error content when I use an HttpLoggingInterceptor, but when I get to the Retrofit OnResponse callback the error body's string is empty.
I can see that there is a body to the error, but I can't seem to pull that body when in the context of the Retrofit callback. Is there a way to ensure the body is accessible there?
Thanks,
Adam
Edit:
The response I see from the server is:
{"error":{"errorMessage":"For input string: \"000001280_713870281\"","httpStatus":400}}
I am trying to pull that response from the response via:
BaseResponse baseResponse = GsonHelper.getObject(BaseResponse.class, response.errorBody().string());
if (baseResponse != null && !TextUtils.isEmpty(baseResponse.getErrorMessage()))
error = baseResponse.getErrorMessage();
(GsonHelper is just a helper which passes the JSON string through GSON to pull the object of type BaseResponse)
The call to response.errorBody().string() results in an IOException: Content-Length and stream length disagree, but I see the content literally 2 lines above in Log Cat

I encountered the same problem before and I fixed it by using the code response.errorBody().string() only once. You'll receive the IOException if you are using it many times so it is advised to just use it as a one-shot stream just as the Documentation on ResponseBody says.
My suggestion is: convert the Stringified errorBody() into an Object immediately as the latter is what you're gonna be using on subsequent operations.

As it was mentioned, you need to use response.errorBody().string() only once. But there is a way to get the error body string more than once.
TL;DR Use the code below to get error body string from response more than once.
public static String getErrorBodyString(Response<?> response) throws IOException {
// Get a copy of error body's BufferedSource.
BufferedSource peekSource = response.errorBody().source().peek();
// Get the Charset, as in the original response().errorBody().string() implementation
MediaType contentType = response.errorBody().contentType(); //
Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
charset = Util.bomAwareCharset(peekSource, charset);
// Read string without consuming data from the original BufferedSource.
return peekSource.readString(charset);
}
Explanation:
This is based on the original response.errorBody().string() method implementation. It uses the copy of BufferedSource from peek() and returns the error body string without consuming it, so you can call it as many times as you need.
If you look at the response.errorBody().string() method implementation, you'll see this:
public final String string() throws IOException {
try (BufferedSource source = source()) {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
}
}
source.readString(charset) consumes data of the error body's BufferedSource instance, that's why response.errorBody().string() returns an empty string on next calls.
To read from error body's BufferedSource without consuming it we can use peek(), which basically returns a copy of the original BufferedSource:
Returns a new BufferedSource that can read data from this
BufferedSource without consuming it.

you can use Gson to get errorBody as your desired model class:
val errorResponse: ErrorMessage? = Gson().fromJson(
response.errorBody()!!.charStream(),
object : TypeToken<ErrorMessage>() {}.type
)

First create an Error class like below:
public class ApiError {
#SerializedName("httpStatus")
private int statusCode;
#SerializedName("errorMessage")
private String message;
public ApiError() {
}
public ApiError(String message) {
this.message = message;
}
public ApiError(int statusCode, String message) {
this.statusCode = statusCode;
this.message = message;
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
}
Second you can create a Utils class to handle your error like below:
public final class ErrorUtils {
private ErrorUtils() {
}
public static ApiError parseApiError(Response<?> response) {
final Converter<ResponseBody, ApiError> converter =
YourApiProvider.getInstance().getRetrofit()
.responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
error = new ApiError(0, "Unknown error"
}
return error;
}
And finally handle your error like below:
if (response.isSuccessful()) {
// Your response is successfull
callback.onSuccess();
}
else {
callback.onFail(ErrorUtils.parseApiError(response));
}
I hope this'll help you. Good luck.

If you are gettig 400 then its a bad request you r trying to send to server.
check your get req.

Related

Retrofit call returning 400, cURL request working perfectly fine, syntax issue

I've tried making a retrofit call to an API endpoint, but it's returning a 400 error, however my curl request is working perfectly fine. I can't seem to spot the error, could someone double check my work to see where I made a mistake?
The curl call that works:
curl --request POST https://connect.squareupsandbox.com/v2/payments \
--header "Content-Type: application/json" \
--header "Authorization: Bearer accesstoken112233" \
--header "Accept: application/json" \
--data '{
"idempotency_key": "ab2a118d-53e2-47c6-88e2-8c48cb09bf9b",
"amount_money": {
"amount": 100,
"currency": "USD"},
"source_id": "cnon:CBASEITjGLBON1y5od2lsdxSPxQ"}'
My Retrofit call:
public interface IMakePayment {
#Headers({
"Accept: application/json",
"Content-Type: application/json",
"Authorization: Bearer accesstoken112233"
})
#POST(".")
Call<Void> listRepos(#Body DataDto dataDto);
}
DataDto class:
public class DataDto {
private String idempotency_key;
private String amount_money;
private String source_id;
public DataDto(String idempotency_key, String amount_money, String source_id) {
this.idempotency_key = idempotency_key;
this.amount_money = amount_money;
this.source_id = source_id;
}
}
And lastly making the retrofit call:
DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", "{\"amount\": 100, \"currency\": \"USD\"}", "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");
RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
Call<Void> call = service.listRepos(dataDto);
call.enqueue(new Callback<Void>() {
#Override
public void onResponse(#NonNull Call<Void> call, #NonNull Response<Void> response) {
Log.d(TAG, "onResponse: " + response.toString());
}
#Override
public void onFailure(#NonNull Call<Void> call, #NonNull Throwable t) {
Log.d(TAG, "onFailure: Error: " + t);
}
});
Retrofit Instance:
public class RetrofitClientInstance {
private static Retrofit retrofit;
private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Edit 1: Changing to second parameter to JSON Object
JSONObject jsonObject = new JSONObject();
try{
jsonObject.put("amount", 100);
jsonObject.put("currency", "USD");
}catch (Exception e){
Log.d(TAG, "onCreate: " + e);
}
DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", jsonObject, "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");
First of all, let's see what 400 means
The HyperText Transfer Protocol (HTTP) 400 Bad Request response status
code indicates that the server cannot or will not process the request
due to something that is perceived to be a client error (e.g.,
malformed request syntax, invalid request message framing, or
deceptive request routing).
Now we are sure, the problem stands in our request (not server fault), most probably it is because you are trying to convert JSON in request (do not do this explicitly GSON will convert automatically)
Use interceptor to verify your outgoing network requests (Tell the result here)
you use #POST(".") which does not make sense, please understand BASE_URL is your server URL NOT MORE
The problem could be translating this post request
So a possible solution
Change base URL into "https://connect.squareupsandbox.com/"
Replace #POST(".") with #POST("v2/payments/")
PS. #NaveenNiraula mentioned right thing even though it did not help you, please follow his instruction, it is the correct way parsing data using GSON (make sure you include it and configure it correctly) converter
EDIT
I make it work (I eliminated 400 error code that is what you want as long as question title is concerned) partially which means I detect why 400 error was occurred and fixed it but unfortunately, I stuck the UNAUTHORIZED issue. The problem was relating to converting json and data type
data class DataDTO(
val idempotency_key: String,
val source_id: String,
val amount_money: MoneyAmount
)
data class MoneyAmount(
val amount: Int,
val currency: String
)
I gist all code here you can refer
You need two DTO classes as below:
public class Amount_money
{
private String amount;
private String currency;
public String getAmount ()
{
return amount;
}
public void setAmount (String amount)
{
this.amount = amount;
}
public String getCurrency ()
{
return currency;
}
public void setCurrency (String currency)
{
this.currency = currency;
}
#Override
public String toString()
{
return "ClassPojo [amount = "+amount+", currency = "+currency+"]";
}
}
And
public class DataDto
{
private String idempotency_key;
private Amount_money amount_money;
private String source_id;
public String getIdempotency_key ()
{
return idempotency_key;
}
public void setIdempotency_key (String idempotency_key)
{
this.idempotency_key = idempotency_key;
}
public Amount_money getAmount_money ()
{
return amount_money;
}
public void setAmount_money (Amount_money amount_money)
{
this.amount_money = amount_money;
}
public String getSource_id ()
{
return source_id;
}
public void setSource_id (String source_id)
{
this.source_id = source_id;
}
#Override
public String toString()
{
return "ClassPojo [idempotency_key = "+idempotency_key+", amount_money = "+amount_money+", source_id = "+source_id+"]";
}
}
You need to create object for each like under :
Amount_money am = new Amount_money();
am.setAmount("100");
am.setCurrency("USD");
DataDto dto = new DataDto();
dto.setIdempotency_key("your key");
dto.setsource_id("your id");
dto.setAmount_money(am);
RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
Call<Void> call = service.listRepos(dataDto);
// yo get the point follow along
Most likely the passed JSON structure is not serialized in the same format.
"amount_money": {
"amount": 100,
"currency": "USD"},
I would at first use for private String amount_money; a real DTO having the amount and currency fields. This should give progress. I'm not 100% sure how the underscore mapping of attributes looks like, but this is the next step.
Add logging to be able to see the passed data. A quick search reveals this tutorial: https://futurestud.io/tutorials/retrofit-2-log-requests-and-responses. When seeing the transmitted data it should be easy to compare the expected and sent data.
Please check your base url.
In your curl you have https://connect.squareupsandbox.com/v2/payments
But in the code you have
private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";
There is extra / (slash) in the end. I've seen cases where it was the issue. Could be your problem :)

How to handle map<dynamic> null object when api is called

How can I manage server response if status is either than 200.
#JsonSerializable(nullable: false)
class LoginResponse {
final String error;
final int status;
final List<User> userList;
LoginResponse({this.error, this.status, this.userList});
factory LoginResponse.fromJson(Map repJson){
List<dynamic> userListResp=repJson['userData'];
List<User> userList=userListResp.map((e)=>User.fromUser(e)).toList();
int s=repJson['status'];
if(s==200){
return LoginResponse(error:repJson['error'],status: repJson['status'],userList:userList);
} else{
return LoginResponse(error:repJson['error'],status: repJson['status']);
}}}
class User{
String cust_id;
String cust_name;
String cust_email;
String cust_mob;
User({this.cust_id,this.cust_name,this.cust_email,this.cust_mob});
factory User.fromUser(Map userJson){
return User(cust_id: userJson['cust_id'],cust_name: userJson['cust_name'],
cust_email: userJson['cust_email'],cust_mob: userJson['cust_mob']);
}
}
server response when an error is occur
{"error":"1","status":201,"message":"Entered email id already exist in our records"}
server response on success
{
"error":"0",
"status":200,
"userData":[
{
"cust_id":"87",
"cust_name":"kio",
"cust_email":"kio1#kio.com",
"cust_gend":null,
"cust_dob":null,
"cust_mob":"098998899889588",
"cust_pass":"e10adc3949ba59abbe56e057f20f883e",
"cust_age":null,
"device_type":"android",
"device_token":"eNWqzDwxqsQ:APA91bF-uK1MI11D3SgHGSw7Omv1imjDrPKBBCrN9JgmyJppHsNVeG5l56EkCCd5ZMaxL_ehQzVhtoEj0fTNB55wYGJt5BqYVvwfAb7HrBqwb_21M6VFPuF6LQINkvE1offQgZYweROO",
"status":"0",
"createAt":"2019-01-31 18:45:19",
"updatedAt":"0000-00-00 00:00:00",
"login_type":"",
"login_id":null,
"is_guest":"0",
"auth_token":"",
"forgot_token":null
}]
}
How can I manage when user data is not present or null, I tried to manage when the status code is 201 but still showing
NoSuchMethodError: The method 'map' was called on null.
To fix your code move the userList mapping inside the if block. This way you will parse the response only of the status code is 200.
int s=repJson['status'];
if (s==200) {
List<dynamic> userListResp=repJson['userData'];
List<User> userList=userListResp.map((e)=>User.fromUser(e)).toList();
return LoginResponse(error:repJson['error'], status:repJson['status'], userList:userList);
} else {
return LoginResponse(error:repJson['error'], status:repJson['status']);
}
However, you might not want to handle errors in your model. It is better to check for error after you performed the request and then decide if you want to parse the response.
Something like this will be easier to handle and won't pollute your model object:
final response = await client.get(requestUrl);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
final loginResponse = LoginResponse.fromJson(json.decode(response.body));
// ...
} else {
// If that call was not successful, throw an error or parse the error object.
throw Exception('Failed to login');
// ...
}

Check which type of data is coming as response using Retrofit

As I'm using Retrofit, I've designed all the POJOs and it was working flawlessly. API is designed in such a way that it will send the required data if the data is of current date or of future dates but not for past dates. In the response, I'll get a JSON response contains a combination of JSON objects and an array as a value of a JSON object and POJOs are according to that. Now if there is no record for present and future dates then I'll receive a string instead of an array and that leads to API error java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING. So what I wanna know if there is any way that I can determine - what I'm receiving an array or a string? and how to update POJO according to that to avoid that error.
JSON response when server has no data
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "No record found in database.",
"Result_Output": "",
"updatedDate": "20-07-2017 10:44:37"
}
JOSN response will be same when server has data but with one difference
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "record found in database.",
"Result_Output": [{multiple elements},
{multiple elements},
{multiple elements}....],
"updatedDate": "20-07-2017 10:44:37"
}
Pojo class named ResponseModel
public class ResponseModel {
private String Result_Code;
private String Result_Status;
private String Result_Message;
private Object Result_Output;
private String updatedDate;
...
}
using Object you can morph as below
call.enqueue(new Callback<ResponseModel>()
{
#Override
public void onResponse(Response<ResponseModel> response, Retrofit retrofit)
{
parseData(); // get other data from ResponseModel Class
if (response.getResultOutput() instanceof List<POJO>)
{
doSomething();
}
else if (response.getResultOutput() instanceof String)
{
doSomething();
}
else //must be error object
{
doSomething();
}
}
#Override
public void onFailure(Throwable t)
{
///Handle failure
}
});
using instanceof you check desired Object type
Where List<POJO> used for multiple elements Model
*check updated solution for parsing ArrayList from response object
hopefully it might be work as you want
catch your json in debug mode and generate pojo class with link below. then compare your class and see difference
http://www.jsonschema2pojo.org/
you can try this method.
try {
callArrayPojo();
} catch (IllegalStateException e) {
callStringPojo();
} catch (Exception e) {
//other}
or you can get ResultMessage generic type
...
private String Result_Code;
private String Result_Status;
private T Result_Message;
...

Trouble get special character in retrofit 2 and gson

I'm trying to get a json list from a web service.
This is the json string return by server :
[{"categoryName":"Política"},{"categoryName":"Economía"},{"categoryName":"Cultura"},{"categoryName":"Deportes"}
The problem is converting in to the POJO. The special characters (í) it's appear like "Pol�tica".
This is the retrofit call function :
#GET("categories")
public Call<List<CategoryPojo>> getCategorias(#Query("sitename") String site)
this is the callback function:
Call<List<CategoryPojo>> call = restservice.getApiService().getCategorias(medio);
try {
call.enqueue(new Callback<List<CategoryPojo>>() {
#Override
public void onResponse(Call<List<CategoryPojo>> call, Response<List<CategoryPojo>> response) {
List<CategoryPojo> categories = response.body();
if (listener != null)
listener.onDataLoaded(categories);
}
#Override
public void onFailure(Call<List<CategoryPojo>> call, Throwable throwable) {
Log.e("Retrofit Error", throwable.getMessage());
}
});
this is the POJO:
public class CategoryPojo implements Serializable{
public CategoryPojo() { }
#SerializedName("categoryName")
private String name;
public String getName()
{
return this.name;
}
}
The result of the request to the Web services, (output in browser) is :
[{"categoryName":"Política"},{"categoryName":"Economía"},{"categoryName":"Cultura"},{"categoryName":"Deportes"},{"categoryName":"Salud"},{"categoryName":"Ciencia y Tecnología"},{"categoryName":"Medio Ambiente"},{"categoryName":"Medios"},{"categoryName":"Militar e Inteligencia"},{"categoryName":"Sociedad"}]
So, the return json has a good encoding...i think that maybe is about the way retrofit read the response.
I'm using retrofit-2.0.2, gson-2.6.1, converter-gson-2.0.2, okhttp-3.2.0.
Any help? please
You should check Content-type in the response headers. Look for the charset value and try to change that on the backend side to application/josn;charset=UTF-8. That worked for me.

Get base64 image using Retrofit error com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:

I am trying to download an Image from a authenticated site. the site returns a base64 version of image. is this the right way to do it on retrofit? how do get the image and set to my image view.
#GET("/img/avatars/{id}")
public void getProfilePic(#Path("id") int id,
Callback<TypedByteArray> result);
i set my restadapter logging to full and the response value looks like this
���V�3��Ωw���Tw�5�vT��>8u�`�j�S�������#���%�A���"Xw��Oq������G#]éG���f�~A#lD�)<���•
not the base64 string.
What I have tried
customResAdapter(ImageService.class).getProfilePic(id, new Callback<TypedByteArray>() {
#Override
public void success(TypedByteArray result, Response response) {
try {
byte[] decodedString = Base64.decode(result.getBytes(), Base64.DEFAULT);
mProfilePic.setImageBitmap(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length));
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void failure(RetrofitError error) {
}
});
i dont know if the following codes are right but currently i get this error message
com.google.gson.JsonSyntaxException:
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
at line 1 column 1 path
If you're using GsonConverter you will always end up in JsonParseException. You need to subclass GsonConverter and avoid JsonParseExceptions and proceed with handling the Response itself.
Validating Json response in your subclassed Converter will give you hint how to handle the deserialization.
In other words, if is valid json you pipeline it to GsonConverter, otherwise decode it from Base64.
public class TypedByteArrayConverter extends GsonConverter {
public TypedByteArrayConverter(Gson gson) {
super(gson);
}
public TypedByteArrayConverter(Gson gson, String charset) {
super(gson, charset);
}
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
//if you are trying to deserialize POJO from json, make a supercall, otherwise convert to TypedByteArray
if(!type.getClass().isAssignableFrom(TypedByteArray.class)) {
return super.fromBody(body, type);
}else {
try {
long length = body.length();
ByteString base64 = ByteString.read(body.in(), (int) length);
return new TypedByteArray(body.mimeType(), base64.toByteArray());
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
ByteString is from Okio package
However, image loading with Retrofit is rather strange idea.

Categories

Resources