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
Related
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
In my Android application I perform some actions related with my Room Database. These actions have to be done in background, this is why I use a threadExecutor. As you can see the code for both methods is almost the same and I was wondering if it would be possible to construct something generic to avoid this code repetition.
public void addOperation(Operation operation, AddOperationInteractor.CallBack callback)
{
Interactor interactor = new AbstractInteractor(ThreadExecutor.getInstance())
{
#Override
public void run()
{
try
{
operationRepository.addNewOperation(operation);
callback.onAddOperationSuccess();
}
catch (Exception ex)
{
callback.onAddOperationSuccess();
}
}
};
interactor.execute();
}
public void deleteOperation(Operation operation, RemoveOperationInteractor.CallBack callback)
{
Interactor interactor = new AbstractInteractor(ThreadExecutor.getInstance())
{
#Override
public void run()
{
try
{
operationRepository.removeOperation(operation);
callback.onRemoveOperationSuccess();
}
catch (Exception ex)
{
callback.onRemoveOperationSuccess();
}
}
};
interactor.execute();
I see no repetition in your code. To reduce boilerplate code, try a lambda:
public void addOperation(Operation operation, AddOperationInteractor.CallBack callback) {
ThreadExecutor.getInstance().execute(() -> {
try {
operationRepository.addNewOperation(operation);
}
finally {
callback.onAddOperationSuccess();
}
});
}
public void deleteOperation(Operation operation, RemoveOperationInteractor.CallBack callback) {
ThreadExecutor.getInstance().execute(() -> {
try {
operationRepository.removeOperation(operation);
}
finally {
callback.onRemoveOperationSuccess();
}
});
}
Now there is only 1 repeating line, to invoke the ThreadExecutor.
Alternatively pass callbacks to a helper method:
public void addOperation(Operation operation, AddOperationInteractor.CallBack callback) {
execute(()-> operationRepository.addNewOperation(operation),
()-> callback.onAddOperationSuccess());
}
public void deleteOperation(Operation operation, RemoveOperationInteractor.CallBack callback) {
execute(()-> operationRepository.removeOperation(operation),
()-> callback.onRemoveOperationSuccess());
}
private void execute(Runnable action, Runnable onSuccess) {
ThreadExecutor.getInstance().execute(() -> {
try {
action.run();
onSuccess.run();
} catch (Exception e) {
LOG.warn(e);
onSuccess.run();
}
}
}
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.
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(...)
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);
}
});