RxJava error handling in chain API calls - java

I have playing with Rxjava recently trying to implement a chain of events(Api callas/Database operations) and seem to have hit a roadblock when it comes to handling errors.
This is what I am trying to do. I am calling an Api that will check if user exists in the database. Based on the response I get, I am trying to chain a few sequences using rxjava. Following diagram might explain a little better.
checkUser()
/ \
No Yes
/ \
createUserRemote() FetchUserNotesRemote()
| |
End SaveUserNotesLocal()
|
End
I am able to chain together checkUser() -> FetchUserNotesRemote() -> SaveUserNotesLocal() sequence with the following code.
checkUser()
.flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
The issue I am mainly trying to solve.
I can't figure out how to handle a case where checkUser() returns
a 404 http status. Because when that happens, subscriber's onError
method gets called which seems to me is what should happen. How can I
handle it so that when I get an error (404) response from API,
instead of executing FetchUserNotesRemote() and SaveUserNotesLocal(),
I execute a different chain of events?
Another thing I am not sure about is, if there is an error called on
any of the observables in a chain, how does the subscriber's onError method know
which observable called it?

1) To execute different chain of observables on error you can use method onErorrResumeNext(). More info here: github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
Example:
checkUser().flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return doSomethingDifferent(); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
2) If the exception is thrown somewhere in your stream, it is passed down to subscriber onError(). If you want to know at which part of stream error was thrown, you can add multiple onErorrResumeNext() calls, that throw concrete exception after each api call.
checkUser()
.onErrorResumeNext(throwable -> { return Observable.error(new CheckUserException()); }
.flatMap(id -> {return fetchData(id);})
.onErrorResumeNext(throwable -> { return Observable.error(new FetchDataException()); }
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return Observable.error(new SaveDataException()); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});

I completely forgot about this. But #mol pushed me in the right direction. My solution was a bit different. This may not be the best solution but it worked for me at the time.
I first created my own custom exception classes like following.
public class CreateUserLocalException extends Exception {
public CreateUserLocalException(String message) {
super(message);
}
}
Then in my checkUser() function I throw exception of type I created above like following.
public Single<String> checkUser(String id) {
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> emitter) throws Exception {
try {
GetUserResponseObject getUserResponseObject = apiClient.usersIdGet(id);
Log.d("Test", "checkUserCall: Status: " + getUserResponseObject.getStatus());
emitter.onSuccess(getUserResponseObject.getBody().getUserId());
} catch (AmazonServiceException e) {
Log.d("Test", "AmazonServiceException : " + e.getErrorMessage());
e.printStackTrace();
if (e.getErrorMessage().equals("timeout")) {
throw new SocketTimeoutException();
} else {
throw new CheckUserException(Integer.toString(e.getStatusCode()));
}
} catch (Exception e) {
e.printStackTrace();
throw new CheckUserException(Integer.toString(AppConstants.ERROR));
}
}
});
}
Then in my chain of calls, in the event of an error, onError(throwable) gets invoked where I am checking the instanceof Exception to identify what kind of exception occurred. Below is the code for chain of functions.
cloudSyncHelper.checkUser(user.getUser_id())
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
})
.flatMap(s -> {
return cloudSyncHelper.createUserLocal(user)
.onErrorResumeNext(throwable -> {
Log.d("Test", "onErrorResumeNext, throwable message: " + throwable.getMessage());
if (throwable instanceof CreateUserLocalException) {
if (Integer.parseInt(throwable.getMessage()) == AppConstants.LOCAL_DB_DUPLICATE) {
return Single.just(user.getUser_id());
}
}
return Single.error(new CreateUserLocalException(Integer.toString(AppConstants.LOCAL_DB_ERROR)));
});
})
.flatMap(id -> {
return cloudSyncHelper.fetchData(id)
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
});
})
.flatMap(notesResponseObject -> {
return cloudSyncHelper.saveFetchedData(notesResponseObject);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onSuccess Called");
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
}
#Override
public void onError(Throwable e) {
if (e instanceof SocketTimeoutException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Socket Time Out");
Utils.createToast(LoginScreen.this, "Socket timed out");
return;
}
int code = Integer.parseInt(e.getMessage());
Log.d("Test", "onError Called");
if (e instanceof CheckUserException) {
Log.d("Test", "onError CheckUserException");
if (code == AppConstants.NOTFOUND) {
newUserSequence(user);
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Utils.createToast(LoginScreen.this, "Unable to user information from cloud. Try again.");
}
}
if (e instanceof CreateUserLocalException) {
Log.d("Test", "onError CreateUserLocalException");
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
}
if (e instanceof FetchDataException) {
Log.d("Test", "onError FetchDataException");
if (code == AppConstants.NOTFOUND) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Unable to fetch data from cloud");
Utils.createToast(LoginScreen.this, "Unable to fetch data from cloud. Try again.");
}
}
if (e instanceof SaveDataLocalException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onError SaveDataLocalException");
if (code == AppConstants.LOCAL_DB_ERROR) {
Log.d("Test", "Unable to save data fetched from cloud");
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
} else {
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
}
}
}
});
Hope this helps.

Related

The exception was not handled due to missing onError handler in the subscribe() method call

I am calling the request it self in a base class
#Override
public Single<BaseResponse<D>> call() {
Single<BaseResponse<D>> singleResponse = Single.create(emitter -> request().getAsOkHttpResponseAndParsed(typeToken(), new OkHttpResponseAndParsedRequestListener<BaseData<D>>() {
#Override
public void onResponse(Response okHttpResponse, BaseData<D> response) {
try {
BaseResponse<D> r = new BaseResponse<D>(okHttpResponse) {
};
r.setData(response.getData());
r.setStatus(response.getStatus());
emitter.onSuccess(r);
} catch (Throwable throwable) {
Log.e("error", "throwable");
emitter.onError(throwable);
}
}
#Override
public void onError(ANError anError) {
Log.e("error", "error");
emitter.onError(anError);
}
}));
singleResponse.doOnSubscribe(__ ->
EspressoIdlingResource.countingIdlingResource.increment()).
doFinally(EspressoIdlingResource.countingIdlingResource::decrement).subscribe();
return singleResponse;
}
then I have my single observer builder class
public class SingleObserverBuilder<T> extends BaseObserverBuilder<T, SingleObserver<T>> {
private final Consumer<Disposable> disposableConsumer;
public SingleObserverBuilder(#NonNull Consumer<Disposable> disposableConsumer) {
this.disposableConsumer = disposableConsumer;
}
#Override
public SingleObserver<T> build() {
return new SingleObserver<T>() {
#Override
public void onSubscribe(Disposable d) {
disposableConsumer.accept(d);
}
#Override
public void onSuccess(T t) {
if (onSuccessListeners() != null) {
for (Consumer<T> onSuccess : onSuccessListeners()) {
onSuccess.accept(t);
}
}
}
#Override
public void onError(Throwable e) {
Log.e("error", "base error");
if (onFailureListeners() != null)
for (Consumer<Throwable> onFailure : onFailureListeners()) {
onFailure.accept(e);
}
}
};
}
}
and in my base observer fragment I access this single observer
public abstract class BaseObserverFragment extends Fragment {
protected CompositeDisposable disposable = new CompositeDisposable();
protected <T> SingleObserverBuilder<T> getDefaultSingleObserver(Object tag) {
SingleObserverBuilder<T> builder = new SingleObserverBuilder<>(disposable -> this.disposable.add(disposable));
builder.tag(tag).onSuccess(object -> {
Logger.e(builder.tag().toString(), "onSuccess");
}).onFailure(throwable -> {
Log.e("error", "base fragment");
Logger.e(builder.tag().toString(), "onFailure");
ErrorLogger.log(getContext(), builder.tag().toString(), throwable);
});
return builder;
}
#Override
public void onDetach() {
super.onDetach();
if(!disposable.isDisposed()) {
Log.e("error", "detach");
disposable.clear();
disposable.dispose();
}
}
}
then in the fragment itself that's how I call api request
viewModel.setAvailabilityStatus(isAvailable)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this.<BaseResponse<ChangeSpAvailabilityResponse>>getDefaultSingleObserver(OperationTag.CHANGE_AVAILABILITY)
.onSuccess(response -> {
})
.onFailure( throwable -> {
Log.e("error", "last");
if (throwable instanceof ANError) {
Log.e("error", "throwable");
if (((ANError) throwable).getErrorCode() == 400) {
((ANError) throwable).getErrorBody();
handleChangeAvailabilityResponse(((ANError) throwable).getErrorAsObject(ChangeSpAvailabilityResponse.class));
}
}
}).build());
}
in that api request I wanna handle failure request body and it all work fine, but sometimes I get this error :
The exception was not handled due to missing onError handler in the
subscribe() method call. Further reading:
https://github.com/ReactiveX/RxJava/wiki/Error-Handling |
com.androidnetworking.error.ANError
and I don't know why or what should I do to handle error
also I have this line
RxJavaPlugins.setErrorHandler(throwable -> {
Log.e("error", throwable.getMessage());
});
in my application class

How to test backPressure in RxJava using Observable?

I want to understand the the need for Flowable in RxJava. So I want to deal with backPressure for huge data with simple Observable. But I am not getting any error with it.
this is my test code:
Observable.range(1, 10000).observeOn(Schedulers.computation())
.subscribe(new Observer<Integer>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onNext(#NonNull Integer integer) {
System.out.println("next: " + integer);
}
#Override
public void onError(#NonNull Throwable e) {
System.out.println("onError " + e.toString());
}
#Override
public void onComplete() {
}
});
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
How can I test backPressure problems using Observable? Thanks

RxJava: one request -> list of integers -> sequence of requests for each int -> result into list

I'm a newbie in RxJava and I have some difficulties while constructing Observable.
My tasks are:
Query to server getExpPointsIdArrayByHouse
On server response. We get an object (RpcDeviceInfoResponse) that contain a list of integers
For each of int value a separate query to server is needed to be executed. Result of the each query is an object "ExpPoint"
Final result is a list of expPoints
What I've already done:
Observable
.defer(new Func0() {
#Override
public Object call() {
try {
return Observable.just(apiHttpClient.getExpPointsIdArrayByHouse(houseId));
} catch (IOException e) {
e.printStackTrace();
return Observable.error(e);
} catch (RightsException e) {
e.printStackTrace();
return Observable.error(e);
}
}
})
.flatMap(new Func1<RpcDeviceInfoResponse, Observable<Integer>>() {
#Override
public Observable<Integer> call(RpcDeviceInfoResponse rpcResponse) {
if (rpcResponse.getResult().size() == 0) {
errorReport(false, context.getResources().getString(R.string.error_while_getting_exp_points), "");
return null;
}
RpcDeviceInfoResponse.TaskListResult result = rpcResponse.getResult().get(0);
return Observable.from(result.getResult());
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
Log.v("onCompleted", "onCompleted");
}
#Override
public void onError(Throwable e) {
Log.v("onError", "onError");
}
#Override
public void onNext(Integer integer) {
Log.v("onNext", "onNext");
}
});
I got stuck at the point 3. I have a list of integers and for each I need to execute a separate query.
You already have an Observable<Int> so you can use flatMap
Observable.defer(
//first query
).flatMap(
// convert result to Observable<Int>
)
.flatMap(
someInt -> doSomeQuery(someInt)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)

Android RxJava asynchronous call in map function

On the change "SortBy", my program will do a NetworkIO to retrieve the top movies and display them.
However, it seems that though I have done subscribeOn(Schedulers.io()), the NetworkIO MovieDB.getPopular() and MovieDB.getTopRated() in the function call in map are excuted on the main thread and I get a android.os.NetworkOnMainThreadException.
I was wondering how to make the public Movie[] call(SortBy sortBy) asynchronous.
sortObservable.map(new Func1<SortBy, Movie[]>() {
#Override
public Movie[] call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return MovieDB.getPopular(); // NETWORK IO
case TOP_RATED:
return MovieDB.getTopRated(); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return new Movie[0];
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
Please check if the below works for you. It uses flatMap instead of map.
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return Observable.just(MovieDB.getPopular()); // NETWORK IO
case TOP_RATED:
return Observable.just(MovieDB.getTopRated()); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return Observable.just(new Movie[0]);
}
}).subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
From your source code on Github, it seems like you are using synchronous mode of executing requests using OkHttp. OkHttp also supports asynchronous requests and that can be preferred. Below would be the changes required in few of the methods.
run method should consume enqueue instead of execute.
Observable<String> runAsync(String url){
return Observable.create(subscriber -> {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) throws IOException {
subscriber.onNext(response.body().string());
}
#Override
public void onFailure(Call call, IOException e) {
subscriber.onError(e);
}
});
});
}
getApi can return an Observable<Movie[]> instead of Movie[]
public Observable<Movie[]> getApiAsync(String type){
return runAsync("http://api.themoviedb.org/3/movie/" + type
+ "?api_key=412e9780d02673b7599233b1636a0f0e").flatMap(response -> {
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(response,
new TypeToken<Map<String, Object>>() {
}.getType());
Movie[] movies = gson.fromJson(gson.toJson(map.get("results")),
Movie[].class);
return Observable.just(movies);
});
}
Finally I sort it out by myself:
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
switch (sortBy) {
case POPULAR:
return Observable.fromCallable(() -> MovieDB.getPopular()).subscribeOn(Schedulers.io());
case TOP_RATED:
return Observable.fromCallable(() -> MovieDB.getTopRated()).subscribeOn(Schedulers.io());
default:
return Observable.fromCallable(() -> new Movie[0]).subscribeOn(Schedulers.io());
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});

How can I throw error in onNext() with RxJava

.subscribe(
new Action1<Response>() {
#Override
public void call(Response response) {
if (response.isSuccess())
//handle success
else
//throw an Throwable(reponse.getMessage())
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//handle Throwable throw from onNext();
}
}
);
I don't wanna handle (!response.isSuccess()) in onNext(). How can I throw it to onError() and handle with other throwable together?
If FailureException extends RuntimeException, then
.doOnNext(response -> {
if(!response.isSuccess())
throw new FailureException(response.getMessage());
})
.subscribe(
item -> { /* handle success */ },
error -> { /* handle failure */ }
);
This works best if you throw the exception as early as possible, as then you can do retries, alternative responses etc. easily.
you can flatMap your response to Response or Error
flatMap(new Func1<Response, Observable<Response>>() {
#Override
public Observable<Response> call(Response response) {
if(response.isSuccess()){
return Observable.just(response);
} else {
return Observable.error(new Throwable(response.getMessage()));
}
}
})
The solution is to add an operator in the middle. My suggestion is to use map as it does not generate new Observable object (in comparison to flatMap which does it):
.map(new Func1<Response, Response>() {
#Override
public Response call(Response response) {
if (response.isSuccess()) {
return response;
} else {
throw new Throwable(reponse.getMessage()));
}
}
})

Categories

Resources