Retrofit2 + RxJava error handling - android

I am using RxJava and Retrofit2 (with OkHttp as the HTTP client) to do networking and am trying to understand how different errors are handled by Retrofit2 and how they look from the RxJava side. The following code illustrates an RxJava Subscriber callback for a network call (made with Retrofit).
Subscription subscription = observable
.subscribeOn(mScheduler)
.observeOn(mAndroidScheduler)
.subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
Timber.d("onCompleted called");
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}
#Override
public void onError(Throwable e) {
Timber.d("onError called");
Timber.d(e.toString());
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}
#Override
public void onNext(User user) {
Timber.d("onNext called");
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
mActivityView.launchMainActivity();
}
});
My question is, in what cases will onError() be called and once it's been called, how can I interrogate the Throwable to determine the cause?
From the Retrofit source it looks like the only Throwables that are possible to see are IOException and HttpException. Can anyone verify that that is true?

Here's the basics: onError() will be called if:
the observable you're subscribing to throws an exception (e.g. you get an IOException while trying to read a file)
an exception is raised in your onNext() method.
If there's an exception in your onComplete(), RxJava will propagate an rx.exceptions.OnCompletedFailedException and if there's an exception in onError() - you'll get rx.exceptions.OnErrorFailedException.
That said, you can just probe the Throwable you receive in your onError() method for exceptions that you're expecting. For example you know that if your API call results in client error (4xx), Retrofit will wrap it into HttpException. If there's a timeout with the request you'll get a SocketTimeoutException. Here's a rough example:
#Override
public void onError(Throwable e) {
Timber.d("onError called");
Timber.d(e.toString());
handleError(e);
}
private handleError(Throwable throwable) {
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException)throwable;
int statusCode = httpException.code();
// handle different HTTP error codes here (4xx)
} else if (throwable instanceof SocketTimeoutException) {
// handle timeout from Retrofit
} else if (throwable instanceof IOException) {
// file was not found, do something
} else {
// generic error handling
mRetainerView.clearUserObservable();
mActivityView.hideProgressBar();
mActivityView.enableUi();
}

Do not use onError for flow. That'd be as bad as try-catch for flow.
Error HTTP codes, are valid responses and you should not deal with them in onError.
You can wrap the return type of your Retrofit services in Result, that gives you the means to get information about what happen with your call without throwing exceptions.
You can handle the state of your app using this pattern:
service.getSomething()
.map(r -> Model.success(r.response()))
.onErrorReturn(Model::error)
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource.loading())
.subscribe(r -> {
myProgressBar.setVisible(r.isLoading());
if (r.isSuccess()) {
handleSuccess(); // e.g. 400 is also success but needs handling
}
if (r.isError()) {
handleError();
}
}, OnErrorNotImplementedException::new);
See how I tried to handle all possible states within the stream and deliberately I throw OnErrorNotImplementedException for something I might've missed. This is very personal but I prefer to crash-fast-and-furious rather than being in an unknown state for a while that later will manifest in a crash harder to debug.

In Kotlin I have used bellow like..
disposable.add(apiService.getLogin_service(parment1,parment1)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<Login_Reg_Data_Model>() {
override fun onSuccess(model: Login_Reg_Data_Model) {
//success
}
override fun onError(e: Throwable) {
if (e is HttpException) {
// We had non-200 http error
Log.e("time exceptionr******>",e.message)
} else if (e is SocketTimeoutException) {
//time exception
Log.e("time exception******>",e.message)
} else if (e is IOException) {
// A network error
Log.e("network error******>",e.message)
} else {
//unknown error
Log.e("unknown error******>",e.message)
}
}
})
)

Related

How to parse error response to Throwable?

I am using Retrofit and RxJava to make network requests like this:
How I am declaring request:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Single<Response<Void>>
How I am calling:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful)
Log.d("----------", "Success!")
else
Log.d("----------", "Not Successfull!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
Some code have been dropped for readability. The probem is even though I get responses back with 401 or 400 statuses, doOnSuccess is being called. Should not the doOnError be called here? I am confused.
As a result my logact is showing "Not Successful" message. How can make sure that doOnErro is called when I get responses back with 401 or 400 statuses?
Or can I parse the incoming response to Throwable and call doOnError() function?
Change the Retrofit API call to return Completable:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Completable
then handle the "success case" via doOnComplete:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnComplete {
Log.d("----------", "Success!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({ }, { })
The real question is, why would you want to throw and exception when the request fails?
The correct processes are being followed here, doOnSuccess is being called as intended because the request has returned a response without encountering an exception being thrown. Regardless of whether the request's response is successful or not.
You should handle the state of your response accordingly and not throw arbitrary exceptions for it:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.isSuccessful()) {
// handle success
} else {
// handle failure
}
}, t -> {
// handle thrown error
yourErrorHandlerMethod(t);
})
The response you getting is correct, the response is shown in doOnSuccess cuz the API you hitting got successfully hit, no matter what was the response code.
doOnError is called when actual API call is failed like network drop in the middle or some server-side issues.
Or can I parse the incoming response to Throwable and call doOnError() function?
You cant do this instead, you can handle the response in doOnSuccess as
try {
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful) // responce code = 200/201
Log.d("----------", "Success!")
else if (it.responseCode == 400 ){
Log.d("----------", "Not Found!")
// Call a method that handles this according to your requirement.
PageNotFoundHandler();
// OPTIONAL throw new UserException();
}
else if (it.responseCode == 401 ){
Log.d("----------", "Not Authorised!")
// Call a method that handles this according to your requirement.
TokenExpiredHandler(); //OR
UnAuthorizedAccessHandler();
// OPTIONAL throw new UserException();
}
else {
Log.d("----------", "Some another Error!")
// Call a method that handles this according to your requirement.
// OPTIONAL throw new UserException();
}
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
} catch
{
ErrorHandler();
}
Or can I parse the incoming response to Throwable and call doOnError() function?
As you mention that you want a throwable, you can achieve it by using the try-catch block.
Just throw a custom EXCEPTION, you have to create a new Custom Exception class for it.

RxJava + GRPC recover from errors and retry requests

I'm trying to recover from errors using RxJava and GRPC. This is my observable:
Observable<Object> observable = Observable.fromCallable(() -> {
try {
Grpc.MyRequest request = Grpc.MyRequest.newBuilder()
.setToken(mToken)
.build();
Grpc.MyResponse reply = mStub.mytest(request);
return reply;
} catch (Exception e) {
///
}
}).cache();
And this is the subscription:
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
})
.subscribe((result) -> {
MyResponse res = ((MyResponse) result);
if (res.getCode()!=0) {
//Check error code and try to refresh token and repeat this request after.
}
},throwable -> {
throwable.printStackTrace();
});
So, when I get the error from my GRPC service, depending on the error code, I want to try and recover from it by doing another request, and then repeating the original request. I'm not sure how to use RxJava retrywhen.
What is the most elegant way of doing something like this?
Error recovery in an observer chain does require a bit of tap dancing, and is by no means elegant. However, it can be contained in the observer chain.
boolean isRecoverable( Throwable t ) {
// this test can be as sophisticated as you want
if ( t instanceof StatusRuntimeException ) {
return true;
}
return false;
}
...
.retryWhen( throwableObservable ->
throwableObservable.flatMap( t -> isRecoverable( t )
? Observable.just("")
: Observable.error( t ) )
...
This approach allows you to decide what you want to do with the error. You could add a delay the just() so that you don't retry immediately. Instead of the just(), you could return an Observable that fetches a new API token.

RxJava RetryWhen, This processor allows only a single Subscriber

I am learning how to do data polling in RxJava2
Here is my code so far.
private io.reactivex.Single<String> getMyTask() {
return io.reactivex.Single.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
Log.d("ERSEN","Task Started!");
Random random = new Random(System.currentTimeMillis());
if(random.nextBoolean()){
return "WORK COMPLETED";
}
Log.d("ERSEN","Task Had An Error!");
throw new IllegalArgumentException();
}
});
}
The above is my Single which emits a String basically simulating some work.
I also make the task randomly succeed and fail to the test the case when a poll event fails to check if re-subscription occurs correctly
My problem
compositeDisposable.add(getMyTask()
.repeatWhen(new Function<Flowable<Object>, Publisher<?>>() {
#Override
public Publisher<?> apply(final Flowable<Object> objectFlowable) throws Exception {
return objectFlowable.delay(INTERVAL, TimeUnit.SECONDS);
}
})
.retryWhen(throwableFlowable -> throwableFlowable.flatMap(new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof ClassCastException) {
return Flowable.error(throwable);
}
return throwableFlowable.delay(INTERVAL, TimeUnit.SECONDS);
}
}))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError));
In the above, I am resubscribing to the Observable when it emitted some data successfully.
I am having problems with retryWhen.
For this example I wish to not retry if a ClassCastException occurs.
In my Observable this is not produced which is for a reason because I am testing the logic to retry only on certain errors
However, I am reviving this error with the above code when an error in the Observable is produced
This processor allows only a single Subscriber
I am not sure what is wrong, I have been following this blog post
http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/
Thanks for reading
Let me know if you would like me to post any more details
You are resubscribing to the error flow in your retryWhen which is not allowed and doesn't make sense in your situation. You should delay a value in flatMap instead:
.retryWhen(throwableFlowable -> throwableFlowable.flatMap(
new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (throwable instanceof ClassCastException) {
return Flowable.error(throwable);
}
return Flowable.just("ignored").delay(INTERVAL, TimeUnit.SECONDS);
}
}
))

RxJava , best way to network check before each api call

I'm using RxJava for my network calls and before each request I need to check the network.
Currently for each request I'm doing like this.
checkNetwork()
.andThen(netWorkCall())
.subscribe(new DisposableSubscriber<String>() {
#Override
public void onNext(String s) {
Log.d(TAG, "onNext: " + s);
}
#Override
public void onError(Throwable t) {
Log.d(TAG, "onError: " + t.getMessage());
}
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
});
}
private Completable checkNetwork() {
return NetworkUtils.isConnected() ? Completable.complete() : Completable.error(new Throwable("Network Error"));
}
private Flowable<String> netWorkCall() {
return Flowable.just("Api response");
}
Is this the recommended way or is there any better way than this? Thanks in advance!!
Usually you don't check the network before. Better try to send the call and catch the errors when the request failed.
In your Repository which query the network you can prevalidate for a connection and throw an exception if the network connection failed.
That means:
networkCall()
.subscribe(
success -> { ... },
error -> { doSomethingWhenErrorHappened() }
);
private Flowable<String> netWorkCall() {
return networkAvailable() ? Flowable.just(...) : Flowable.error(...);
}
Thats the way it goes.
You should consider using a Single or Maybe since a network request usually returns only data onec or an error. Flowable is designed for using a stream.

Prevent OnErrorNotImplementedException

I want to achieve that if i call the Obervable.subscribe(Action1) method, it does not throw OnErrorNotImplementedException anywhere, but if i call Obervable.subscribe(Action1, Action1), the second action is called when an error is raised as normal. I tried two ways:
.onErrorResumeNext(Observable.empty())
This way OnErrorNotImplementedException is not thrown, however if i pass also the second action, the action is never called either.
Second:
.lift(new Observable.Operator<T, T>() {
#Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
return new Subscriber<T>() {
#Override
public void onCompleted() {
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
#Override
public void onError(Throwable e) {
if (!subscriber.isUnsubscribed()) {
try {
subscriber.onError(e);
} catch (Throwable t) {
if (!(t instanceof OnErrorNotImplementedException)) {
throw t;
}
}
}
}
#Override
public void onNext(T t) {
if (!isUnsubscribed()) {
subscriber.onNext(t);
}
}
};
}
});
The problem with this if observeOn() is called later then this will be asynchronous and obviously my exception handling here will not work.
Is there way to achieve this. I wish there would be a subscribe() method which does not throw OnErrorNotImplementedException in onError.
Here is another possible solution, you can define the onNext and a Throwable (also you cannot loose the lambda syntax):
.subscribe(t -> doSomething(t), e -> showError(e));
here's how we do it at work. Instead of making actions we made an abstract NYTSubscriber which has onError and onCompleted implemented. This way you can use this subscriber and only implement the onNext callback OR you can override onError and onCompleted when necessary
public abstract class NYTSubscriber<T> extends Subscriber<T> {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
}
If you are using Kotlin then the syntax is like
.subscribe({success -> doOnSuccess(success)},{error -> doOnError(error)})
When you do like this '.onErrorResumeNext(Observable.empty())' your stream will be completed when error occurs - that's why your actions is never called.
You can user '.retry()' and your stream will be restarted automatically when you have an error.
This Error comes when calling the Subscribe method, but not providing onError() callbacks.
Kotlin
subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
// on successful completion }
override fun onError(e: Throwable) {
//error message
}
}
)

Categories

Resources