I have been developing android app, wheream using Retrofit.
In that how to handle onFailure(Throwable t) callback for NoInternetConnection and OtherError
I have check some questions on stackoverflow, but it didn't helped, because am using retrofit 2
compile 'com.squareup.retrofit2:retrofit:2.1.0'
Callback Code
public class AvsCallBack<T> implements Callback<T> {
private static final String TAG = "AvsCallBack";
private AvsCallbackInterface<T> avsInterface;
private Activity activity;
private boolean validateError = true;
public AvsCallBack(Activity activity, AvsCallbackInterface<T> avsInterface) {
this.activity = activity;
this.avsInterface = avsInterface;
}
public AvsCallBack(Activity activity, AvsCallbackInterface<T> avsInterface, boolean validateError) {
this.activity = activity;
this.avsInterface = avsInterface;
this.validateError = validateError;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
if (BuildConfig.DEBUG) Log.d(TAG, new Gson().toJson(response.body()));
avsInterface.onSuccess(call, response.body());
} else {
onFailure(call, null);
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
if (validateError) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Retrofit Exception -> " + ((t != null && t.getMessage() != null) ? t.getMessage() : "---"));
if (t != null && (t instanceof IOException || t instanceof SocketTimeoutException || t instanceof ConnectException)) {
if (t instanceof SocketTimeoutException || t instanceof TimeoutException) {
((BaseActivity) activity).showToast("Oops something went wrong");
//avsInterface.onError(call, new AvsException("Oops something went wrong, please try again later..."));
} else {
((BaseActivity) activity).showToast("Please check your internet connection...");
//avsInterface.onError(call, new AvsException("Please check your internet connection..."));
}
} else {
((BaseActivity) activity).showToast("Oops something went wrong");
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Avs Exception -> " + ((t != null && t.getMessage() != null) ? t.getMessage() : "---"));
}
avsInterface.onError(call, t);
}
}
MyInterface
public interface AvsCallbackInterface<T> {
void onSuccess(Call<T> call, T t);
void onError(Call<T> call, Throwable throwable);
}
Quote from here:
When Throwable is passed to the failure, the callback is an
IOException, this means that it was a network problem (socket
timeout, unknown host, etc.). Any other exception means something
broke either in serializing/deserializing the data or it's a
configuration problem.
You can do t instanceof IOException to determine network problem and
react appropriately.
A 401 (or any non-2xx response code) will actually go to the response
callback, because it was a successful response even though it may not
have been a successful operation on the server. You can check this in
onResponse by calling response.isSuccess().
What I do is use a interceptor for request which checks for internet connectivity and responds accordingly. In my case I use MPV so it automatically call BasePresenter method.
Interceptor:
class NetworkStatusInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
if (!Util.isInternet(mContext)) {
return new Response.Builder()
.code(1007)
.request(chain.request())
.protocol(Protocol.HTTP_2)
.body(ResponseBody.create(MediaType.parse("{}"),"{}"))
.message(mContext.getString(R.string.warning_no_internet))
.build();
}
return chain.proceed(chain.request());
}
}
Attach interceptor to OkHTTPClient:
builder.addInterceptor(new NetworkStatusInterceptor());
You can handle this in your onResponse:
public void onResponse(final Call<T> call, final Response<T> response) {
if (response.code() == 1007) {
// handle no internet error
}
}
Related
I am using retrofit to retrieve data from my server - it is a user search:
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<List<Movie>> call = apiService.getSearch(lng, 0, newText);
call.enqueue(new Callback<List<Movie>>() {
#Override
public void onResponse(#NonNull Call<List<Movie>> call, #NonNull Response<List<Movie>> response) {
movieList = response.body();
if (dialog != null) {
dialog.dismiss(); dialog = null;
}
if (response.isSuccessful()) {
recyclerAdapter.setMovieList(movieList);
} else {
showMsgSnack(getString(R.string.Nodata));
}
}
#Override
public void onFailure(#NonNull Call<List<Movie>> call, #NonNull Throwable t) {
if (dialog != null) {
dialog.dismiss(); dialog = null;
}
if(t instanceof UnknownHostException){
showMsgSnack(getString(R.string.Network));
}
else if(t instanceof SocketTimeoutException){
showMsgSnack(getString(R.string.ServerTimeout));
}
else {
showMsgSnack(getString(R.string.ServerError));
}
}
});
Json is generaed via PHP in the server. If the search is success, json will return like [{"title": "sometitle",...}].
But if there is no match in database, I am returning just an empty {}.
But in android it goes to the failure and shows ServerError message. I want to show in that case some message as No match found.
How can I handle this response?
I also created a Default callback for Nullpointerexception, but this still doesn't work:
call.enqueue(new DefaultCallback<>(new Callback<List<Movie>>() {
#Override
public void onResponse(#NonNull Call<List<Movie>> call, #NonNull Response<List<Movie>> response) {
movieList = response.body();
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
if (response.isSuccessful()) {
recyclerAdapter.setMovieList(movieList);
} else {
showMsgSnack(getString(R.string.Nodata));
}
}
#Override
public void onFailure(#NonNull Call<List<Movie>> call, #NonNull Throwable t) {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
if (t instanceof UnknownHostException) {
showMsgSnack(getString(R.string.Network));
}
if (t instanceof NullPointerException) {
showMsgSnack(getString(R.string.Nodata));
} else if (t instanceof SocketTimeoutException) {
showMsgSnack(getString(R.string.ServerTimeout));
} else {
showMsgSnack(getString(R.string.ServerError));
}
}
}));
}
public static class DefaultCallback<T> implements Callback<T> {
private static final String TAG = "YOUR_TAG";
private final Callback<T> callback;
public DefaultCallback(Callback<T> callback) {
this.callback = callback;
}
#Override
public void onResponse(#NonNull Call<T> call, Response<T> response) {
if (response.body() == null) {
callback.onFailure(call, new NullPointerException("Empty response"));
} else {
callback.onResponse(call, response);
}
}
#Override
public void onFailure(#NonNull Call<T> call, Throwable t) {
Log.e(TAG, t.toString());
callback.onFailure(call, t);
}
}
It says: YOUR_TAG: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
In android after receiving response from API you want List of type Movie object (List) which is like [{"title": "sometitle",...}] this.
But when you don't find anything on search then you are returning " {} "
And here you are getting an issue, this is because you are excepting List means Array and API returning "{}" means Object.
Simple solution don't send object "{}" send Array "[]" and check
#Override
public void onResponse(#NonNull Call<List<Movie>> call, #NonNull Response<List<Movie>> response) {
movieList = response.body();
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
if (movieList.size() > 0) {
recyclerAdapter.setMovieList(movieList);
} else {
showMsgSnack(getString(R.string.Nodata));
}
}
Hope you understand and this will help!
I've a backend that returns 200 HTTP code even if the request had errors:
{
"error": {
"message": "Generic error",
"code": 13000
}
}
Now, how can I access raw response body, after using:
.addConverterFactory(GsonConverterFactory.create(gson))
I've tried:
response.raw().body().string()
But I get:
java.lang.IllegalStateException: Cannot read raw response body of a converted body.
I've implemented a generic APICallback class, that should "block" the success and fire an APIError event:
abstract class APICallback<T> implements Callback<T> {
abstract void onSuccess(Call<T> call, T result);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.body() != null) {
if (response.body() instanceof APIError) {
// this is not working
} else {
onSuccess(call, response.body());
}
} else {
apiError = new APIError("Unknown error");
bus.post(new APIErrorEvent(apiError));
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
String message = t.getLocalizedMessage() != null ? t.getLocalizedMessage() : "Unknown error";
apiError = new APIError(message);
bus.post(new APIErrorEvent(apiError, source));
}
}
I'd like to mantain the "auto-converter" capability...many thanks in advance.
For error response you have to take response.errorBody() not response.body()
abstract class APICallback<T> implements Callback<T> {
abstract void onSuccess(Call<T> call, T result);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.body() != null) {
if (response.body() instanceof APIError) {
// this is not working
} else {
onSuccess(call, response.body());
}
} else {
apiError = new APIError("Unknown error");
bus.post(new APIErrorEvent(apiError));
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
String message = t.getLocalizedMessage() != null ? t.getLocalizedMessage() : "Unknown error";
apiError = new APIError(message);
bus.post(new APIErrorEvent(apiError, source));
}
}
change to
abstract class APICallback<T> implements Callback<T> {
abstract void onSuccess(Call<T> call, T result);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
onSuccess(call, response.body());
}
} else {
if (response.errorBody() != null) {
if (response.errorBody() instanceof APIError) {
}
} else {
apiError = new APIError("Unknown error");
bus.post(new APIErrorEvent(apiError));
}
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
String message = t.getLocalizedMessage() != null ? t.getLocalizedMessage() : "Unknown error";
apiError = new APIError(message);
bus.post(new APIErrorEvent(apiError, source));
}
}
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..!!!
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);
I want to implement a error handling mechanism using Retorfit 2.
The solutions that are available are using RetrofitError class which I can't find in the current repo.
If you are making synchronous request, you define your request method in the interface as Call<List<Car>>.
Once you execute the request you receive response and deserialized data wrapped in Response<T> as Response<List<Car>>. This wrapped gives you access to headers, http codes and raw response body.
You can access error body as:
Call<List<Car>> carsCall = carInterface.loadCars();
try {
Response<List<Car>> carsResponse = carsCall.execute();
} catch (IOException e) {
e.printStackTrace();
//network Exception is throw here
}
if(carsResponse != null && !carsResponse.isSuccess() && carsReponse.errorBody() != null){
// handle carsResponse.errorBody()
}
For async calls, you receive Throwable, if I/O exception is thrown during the network call:
Call<List<Car>> call = service.loadCars();
call.enqueue(new Callback<List<Car>>() {
#Override
public void onResponse(Response<List<Car>> response) {
// Get result from response.body(), headers, status codes, etc
}
#Override
public void onFailure(Throwable t) {
//handle error
}
});
Call<List<data>> call = MyService.loadData();
call.enqueue(new Callback<List<data>>() {
#Override
public void onResponse(Call<List<data>> call, Response<List<data>> response) {
if (response.isSuccessful()) {
//Response success. Handle data here
}
else{
//For getting error message
Log.d("Error message",response.message());
//For getting error code. Code is integer value like 200,404 etc
Log.d("Error code",String.valueOf(response.code()));
}
}
#Override
public void onFailure(Call<List<data>> call, Throwable t) {
if (t instanceof IOException){
//Add your code for displaying no network connection error
}
});
The simple method:
int code = response.raw().code();
String message = response.raw().message();