I'm working on Retrofit 2.0 (which is awesome) to handle API responses.
All works well when API answers with success, I return the converted object wished from the json response
Here an example of request:
ServiceAPI.getUser(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
User user = response.body(); // user is my POJO
}
#Override
public void onFailure(Call<User> call, final Throwable t) {
Log.e(TAG, "Error: " + t.getMessage())
}
});
When response is done, I return the POJO (which is my main purpose), not the json to parse in order to avoid boilerplate.
So here my code to handle this :
// the interface to handle calls
protected interface ServiceAPI {
#GET("/user/{userId}")
Call<User> getUser(#Path("userId") String userId);
}
// the GSON part for converting data
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ItemTypeAdapterFactory())
.setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'")
.create();
// an interceptor to log requests responses
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(new LogJsonInterceptor())
.build();
// the retrofit builder
retrofit = new Retrofit.Builder()
.baseUrl(mBaseUrl)
.client(okClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
// the interceptor to log requests
public static class LogJsonInterceptor implements Interceptor {
#Override
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
String rawJson = response.body().string();
Log.d(TAG, rawJson);
// Re-create the response before returning it because body can be read only once
return response.newBuilder().body(ResponseBody.create(response.body().contentType(), rawJson)).build();
}
}
// here the magic to handle json response and get data from "data" json key
public static class ItemTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
private RestError error;
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data")) {
jsonElement = jsonObject.get("data");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
// here the method to call from an activity for example to get an User
public static void getUser(final String userId, final Callback<User> callback) {
serviceAPI.getUser(userId).enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
callback.onResponse(call, response);
}
#Override
public void onFailure(Call<User> call, Throwable t) {
callback.onFailure(call, t);
}
});
}
// here the call from the Activity
ServiceAPI.getUser(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
User user = response.body();
}
#Override
public void onFailure(Call<User> call, final Throwable t) {
Log.e(TAG, "Error: " + t.getMessage())
}
});
// when API answers with success (code 200 from headers)
{
"data":
{
"id":1,
"name":"myname",
"email":"myemail"
}
}
So all works well here because I get response from "data" and convert the response into my POJO
PROBLEM:
But when API answers with an error (also with code 200 from headers), I get this:
{
"error":
{
"code":200
"type":"OAuth_exception",
"message":"You need an access token to get an user",
}
}
The problem is that the retrofit "response" is nevertheless successfull and the errorBody is null
So here, I would to convert this into a RestError POJO (below) and send it inside the call when calling getUser method
public class RestError {
private int code;
private String message;
private String type;
public RestError(int code, String message, String type) {
this.code = code;
this.message = message;
this.type = type;
}
}
Any idea to fix that?
UPDATE:
I added this in the ItemTypeAdatperFactory
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data")) {
jsonElement = jsonObject.get("data");
} else if (jsonObject.has("error")) {
jsonElement = jsonObject.get("error");
TypeAdapter<RestError> restErrorTypeAdapter = gson.getAdapter(RestError.class);
RestError error = restErrorTypeAdapter.fromJsonTree(jsonElement);
return (T) error;
}
}
return delegate.fromJsonTree(jsonElement);
}
And I created a custom Callback like this:
public abstract static class CustomCallback<T> implements Callback<T> {
public abstract void onError(RestError error);
public abstract void onSuccess(T body);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if(response.body() instanceof RestError) {
onError((RestError) response.body());
} else {
onSuccess(response.body());
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
Log.e(TAG, t.toString());
}
}
So now how the call is:
ServiceAPI.getUser(new ServiceAPI.CustomCallback<User>() {
#Override
public void onError(RestError error) {
Log.e(TAG, error.toString());
}
#Override
public void onSuccess(User body) {
Log.d(TAG, body.toString());
User user = body;
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.e(TAG, t.toString());
}
});
So now when an error occurs I get it from onError(), otherwise onSuccess() and seems to do the job
What do you think about?
You can extend all your POJOs from some Response class, which will contain field RestError error. Then you can make some isSuccessful method, which will check is error field null or not and handle it properly.
In my view, you can customize your ItemTypeAdapterFactory like that to check if the reponse is your data or it is an error
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data")) {
jsonElement = jsonObject.get("data");
} else if(jsonObject.has("error"){
jsonElement = jsonObject.get("error");
}
}
//TODO: need to handle your parsing here (to data or to error)
return delegate.fromJsonTree(jsonElement);
}
Related
I want to take jsonobject using retrofit without class definition, the result is null. How about this ?
I have a get query with JSON response :
[{"Kuota":"12"}]
This my code get data JSONObject .
public void GetKuota(String Key) {
IBookingService iBookingService = APIClient.getClient().create(IBookingService.class);
Call<ResponseBody> call = iBookingService.getKuota(Key);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(!response.isSuccessful()) {
JSONObject jsonObject;
try {
jsonObject = new JSONObject(response.body().toString());
// Log.d(jsonObject.getString("Kuota"));
datas =String.valueOf(jsonObject.getInt("Kuota"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
Suggestion:
Create a model class, lets say Kuota
public class Kuota {
public int Kuota;
}
Then rewrite the Retrofit callback like this.
iBookingService.getKuota(Key).enqueue(new Callback<List<Kuota>>() {
#Override
public void onResponse(Call<List<Kuota>> call, Response<List<Kuota>> response) {
if(response.code() == 200 && response.body() != null) {
try {
for(Kuota k: response.body()) {
Log.d("Kuota Value" , " " + k.kuota);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<List<Kuota>> call, Throwable t) {
}
});
Replace iBookingService data holder from ResponseBody to List< Kuota >
if not added, please add the JSON serializer while setting up Retrofit instance.
new Retrofit.Builder()
.......
.addConverterFactory(GsonConverterFactory.create())
.build();
And also add the dependency
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
I have fragment and Common class which inside it used retrofit callback..I have Connect_and_get class.It sends request to server and gets information.I must use this information in my fragment.. But I can't return result onResponse.How can I do it..(Response is coming well from server)
Please see my code
public class Connect_and_Get {
private int size;
private OkHttpClient.Builder httpClient;
private ApiService client;
private Call<Response> call;
private MyPreference myPreference;
String a[] = {"secret"};
String b[] = {"secret"};
public int Connect_and_Get() {
Requests request;
request = new Requests("tasks.list", new params(20, 0, a, b, "", "", "", "", ""));
httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", "Bearer " + "secret").build();
return chain.proceed(request);
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
client = retrofit.create(ApiService.class);
call = client.getDocument(request);
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
size = response.body().getResult().getList().size();
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
}
});
//retruning information
return size;
}
}
and result from common class coming 0;Because it doesn't wait my response so it is returning 0;
In fragment
Connect_and_Get a = new Connect_and_Get();
int getting = a.Connect_and_Get();
Log.d("mylog", "result:"+String.valueOf(getting));
Declare an interface like this
public interface ResponseListener {
public int onResponse(int size);
}
and use below code in your activity
public class MainActivity extends AppCompatActivity implements ResponseListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Connect_and_Get().Connect_and_Get(this);
}
#Override
public int onResponse(int size) {
// to do
return 0;
}
}
modify your connect class like this
public class Connect_and_Get {
public int Connect_and_Get(ResponseListener responseListener) {
// as it was
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
size = response.body().getResult().getList().size();
responseListener.onResponse(size);
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
}
});
}
}
You need to check whether your response is successful or not.
Check the code below.
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
if(response.isSuccessful()){
//enter code here
} else {
//Error Message
}
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
Log.e("Log", "Error -> "+t.getLocalizedMessage());
}
});
You can use an event bus like (rxbus,otto, etc..) to post events across your app when the response from the api ready to use .
Retrofit callback sample code:
call.enqueue(new Callback<Response>() {
#Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
Bus.getInstance.post(Event)
}
#Override
public void onFailure(Call<Response> call, Throwable t) {
}
});
Fragment sample:
#Override
protected void onCreate(Bundle savedInstanceState) {
Bus.getInstance.register(this)
}
#Subscribe
public void onCallDone(Event response) {
//enter code here
}
hi i am using retrofit my callback is as follow
#Override
public void onResponse(final Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
passing this to my view
} else {
// as this failed other then 200 retroCallback.onFailure(call, new Throwable(""));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
}
so in this how i can pass my ErrorBean instead of Throwable anyway we can pass custom model in onFailure ? as my server giving me response in some formate i want to pass that format .. i am using retrofit 2.1.0
You can subclass Throwable and pass additional object using composition.
public class ErrorBean extends Throwable {
public ErrorPayload payload = null;
public ErrorBean(ErrorPayload payload) {
this.payload = payload;
}
}
Then, in onError:
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
if (t instanceof ErrorBean) {
// do your stuff here
((ErrorBean)t).payload.text;
}
}
AFAIK,, Retrofit's onFailure is used for handling errors like no internet connection.
To handle the error response from your Server, Error response, I mean response from Server with 4xx status code but with some JSON response for client to handle it.
Say, you are getting this error structure from Server:
{
statusCode: 409,
message: "Email address already registered"
}
This error will be captured in onResponse(...). To handle this, create your
public class ErrorBean {
private int statusCode;
private String message;
public ErrorBean() {
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
}
Create a simple ErrorHandler util:
public class ErrorUtils {
public static ErrorBean parseError(Response<?> response) {
Converter<ResponseBody, ErrorBean> converter =
ServiceGenerator.retrofit()
.responseBodyConverter(ErrorBean.class, new Annotation[0]);
ErrorBean error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return new ErrorBean();
}
return error;
}
}
And finally,
...
call.enqueue(new Callback<SuccessResponse>() {
#Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
if (response.isSuccessful()) {
// use response data and do some fancy stuff :)
} else {
// parse the response body …
ErrorBean error = ErrorUtils.parseError(response);
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message());
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
// there is more than just a failing request (like: no internet connection)
}
});
Hope you got the point..!!!
I want to read response as a custom class but I have to use ResponseBody as a parameter in Post method.
Post interface :
public interface IPostPhoneToken {
#FormUrlEncoded
#POST()
Call<ResponseBody> postPhoneToken(
#Field("data[UserPhoneToken][first_name]") String firstName,
...
#Url String endpoint);
}
Problem is here :
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response.isSuccessful()){
}
else{
System.out.println(response.message());
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
System.out.println("Failed");
}
});
I want to change ResponseBody with different Class to be able to read response values.
Thanks.
You can use Response<JsonElement> and get as Json object your response and after that use any json deserializer to convert to your class.
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if(response.isSuccessful()){
JsonElement jsonElement = response.body();
JsonObject objectWhichYouNeed = jsonElement.getAsJsonObject();
//use any json deserializer to convert to your class.
}
else{
System.out.println(response.message());
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
System.out.println("Failed");
}
});
So when I make a POST API call to my server, I get a 400 Bad Request error with JSON response.
{
"userMessage": "Blah",
"internalMessage": "Bad Request blah blah",
"errorCode": 1
}
I call it by
Call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//AA
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//BB
}
}
However the problem is that once I get the response, onFailure() is invoke so that //BB is called. Here, I have no way to access the JSON response.
When I log the api request and response, it doesn't show JSON response at all. And Throwable t is IOException. However, strangely, when I make the same call on Postman, it does return the expected JSON response with 400 error code.
So my question is how can I get the json response when I get 400 Bad Request error? Should I add something to okhttpclient?
Thanks
You can do it in your onResponse method, remember 400 is a response status not an error:
if (response.code() == 400) {
Log.v("Error code 400",response.errorBody().string());
}
And you can handle any response code except 200-300 with Gson like that:
if (response.code() == 400) {
Gson gson = new GsonBuilder().create();
ErrorPojoClass mError=new ErrorPojoClass();
try {
mError= gson.fromJson(response.errorBody().string(),ErrorPojoClass.class);
Toast.makeText(context, mError.getDescription(), Toast.LENGTH_LONG).show();
} catch (IOException e) {
// handle failure to read error
}
}
Add this to your build.gradle : compile 'com.google.code.gson:gson:2.7'
If you want create Pojo class go to Json Schema 2 Pojo and paste your example Json response. Select source type Json and annotation Gson .
You can try the below code to get 400 response. You can get error response from errorBody() method.
Call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//get success and error response here
if (response.code() == 400) {
if(!response.isSuccessful()) {
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(response.errorBody().string());
String userMessage = jsonObject.getString("userMessage");
String internalMessage = jsonObject.getString("internalMessage");
String errorCode = jsonObject.getString("errorCode");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//get failure response here
}
}
}
EDIT: Fixed method name from toString to string
Handle ErrorResponse with your class object
Kotlin
val errorResponse = Gson().fromJson(response.errorBody()!!.charStream(), ErrorResponse::class.java)
Java
ErrorResponse errorResponse = new Gson().fromJson(response.errorBody.charStream(),ErrorResponse.class)
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
DialogHelper.dismiss();
if (response.isSuccessful()) {
// Success
} else {
try {
JSONObject jObjError = new JSONObject(response.errorBody().string());
Toast.makeText(getContext(), jObjError.getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
First step:
Create your POJO class for error response. In my case, ApiError.java
public class ApiError {
#SerializedName("errorMessage")
#Expose
private String errorMessage;
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage= errorMessage;
}
}
Second Step:
Write below code in your api callback.
Call.enqueue(new Callback<RegistrationResponse>() {
#Override
public void onResponse(Call<RegistrationResponse> call, Response<RegistrationResponse> response)
{
if (response.isSuccessful()) {
// do your code here
} else if (response.code() == 400) {
Converter<ResponseBody, ApiError> converter =
ApiClient.retrofit.responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError error;
try {
error = converter.convert(response.errorBody());
Log.e("error message", error.getErrorMessage());
Toast.makeText(context, error.getErrorMessage(), Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<RegistrationResponse> call, Throwable t) {
//do your failure handling code here
}
}
Here ApiClient.retrofit is your retrofit instance which is static.
I got similar issue, but existing code was stick to RxJava 2 chain.
Here's my solution:
public static <T> Observable<T> rxified(final Call<T> request, final Class<T> klazz) {
return Observable.create(new ObservableOnSubscribe<T>() {
AtomicBoolean justDisposed = new AtomicBoolean(false);
#Override
public void subscribe(final ObservableEmitter<T> emitter) throws Exception {
emitter.setDisposable(new Disposable() {
#Override
public void dispose() {
request.cancel();
justDisposed.set(true);
}
#Override
public boolean isDisposed() {
return justDisposed.get();
}
});
if (!emitter.isDisposed())
request.enqueue(new Callback<T>() {
#Override
public void onResponse(Call<T> call, retrofit2.Response<T> response) {
if (!emitter.isDisposed()) {
if (response.isSuccessful()) {
emitter.onNext(response.body());
emitter.onComplete();
} else {
Gson gson = new Gson();
try {
T errorResponse = gson.fromJson(response.errorBody().string(), klazz);
emitter.onNext(errorResponse);
emitter.onComplete();
} catch (IOException e) {
emitter.onError(e);
}
}
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
if (!emitter.isDisposed()) emitter.onError(t);
}
});
}
});
}
transforming 400-like responses into rx chain is pretty simple:
Call<Cat> request = catApi.getCat();
rxified(request, Cat.class).subscribe( (cat) -> println(cat) );
Here is the simplest solution,
If you want to handle the response from onFailure method:
#Override
public void onFailure(Call<T> call, Throwable t) {
HttpException httpException = (HttpException) t;
String errorBody = httpException.response().errorBody().string();
// use Gson to parse json to your Error handling model class
ErrorResponse errorResponse = Gson().fromJson(errorBody, ErrorResponse.class);
}
Or if you are using rxjava Observable with Kotlin, handle it from error body:
{ error ->
val httpException :HttpException = error as HttpException
val errorBody: String = httpException.response().errorBody()!!.string()
// use Gson to parse json to your Error handling model class
val errorResponse: ErrorResponse =
Gson().fromJson(errorBody, ErrorResponse::class.java)
}
Don't forget to properly handle json to class conversion (use try-catch if not sure).
simply use
if (throwable is HttpException && (throwable!!.code() == 400 || throwable!!.code()==404)){
var responseBody = throwable!!.response()?.errorBody()?.string()
val jsonObject = JSONObject(responseBody!!.trim())
var message = jsonObject.getString("message")
tvValMsg.set(message)
}
This is how you can handle the response message
I am handling for error 500 you can add as much you want
switch (response.code()) {
case HttpURLConnection.HTTP_OK:
break;
case HttpURLConnection.HTTP_UNAUTHORIZED:
callback.onUnAuthentic();
break;
case HttpURLConnection.HTTP_INTERNAL_ERROR:
try {
String errorResponse = response.errorBody().string();
JSONObject object = new JSONObject(errorResponse);
String message = "Error";
if (object.has("Message"))
message = String.valueOf(object.get("Message"));
callback.onError(message);
} catch (IOException e) {
e.printStackTrace();
}
break;
case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:
case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
default:
callback.onNetworkError();
break;
}
IF you are getting 400(Bad Request) by using retrofit first make sure are are setting input to API is Only Model class, If not then replace input request by Model class and then check you will get Success response.
#POST("api/users/CreateAccount")
Call<CreateAccount> createAccount(#Body CreateAccount model, #Header("Content-Type") String content_type);