My use case is: I have an Android app, I need to call one api, when that data becomes available call the second api.
For this I was planning to use RxAndroid, with retries.
I was able to do 1 api call with retries, but I need to chain the 2, and be able to show 2 different errors, one for call 1 not available, and one for call2 not available.
I can have the retry either on the whole chain, but I would prefer to retry each operation individually.
My code is as follow, I need to add a "callApi1()" before the callApi2, and I would prefer, like I said to have a different observable with retries.
Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
try {
subscriber.onNext(callApi2());
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
})
.retryWhen(new RetryWithDelay(20, 3000))
.timeout(TIME_OUT_SECONDS, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<String>() {
#Override
public void onNext(String data) {
//show something
}
....
}
You could use the Observable.concat operator e.g.
Observable<String> obs1 = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
try {
subscriber.onNext(callApi1());
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
})
.timeout(TIME_OUT_SECONDS, TimeUnit.SECONDS)
.retryWhen(new RetryWithDelay(20, 3000))
.subscribeOn(Schedulers.io());
Observable<String> obs2 = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
try {
subscriber.onNext(callApi2());
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
})
.timeout(TIME_OUT_SECONDS, TimeUnit.SECONDS)
.retryWhen(new RetryWithDelay(20, 3000))
.subscribeOn(Schedulers.io());
Observable<String> chained = Observable.concat(obs1,obs2);
Related
I have a Asyn call thrift interface:
public CompletableFuture<List<Long>> getFavourites(Long userId){
CompletableFuture<List<Long>> future = new CompletableFuture();
OctoThriftCallback callback = new OctoThriftCallback(thriftExecutor);
callback.addObserver(new OctoObserver() {
#Override
public void onSuccess(Object o) {
future.complete((List<Long>) o);
}
#Override
public void onFailure(Throwable throwable) {
future.completeExceptionally(throwable);
}
});
try {
recommendAsyncService.getFavorites(userId, callback);
} catch (TException e) {
log.error("OctoCall RecommendAsyncService.getFavorites", e);
}
return future;
}
Now it returns a CompletableFuture<List>. And then I call it to do some processor by using Flux.
public Flux<Product> getRecommend(Long userId) throws InterruptedException, ExecutionException, TimeoutException {
// do not like it
List<Long> recommendList = wrapper.getRecommend(userId).get(2, TimeUnit.SECONDS);
System.out.println(recommendList);
return Flux.fromIterable(recommendList)
.flatMap(id -> Mono.defer(() -> Mono.just(Product.builder()
.userId(userId)
.productId(id)
.productType((int) (Math.random()*100))
.build())))
.take(5)
.publishOn(mdpScheduler);
}
However, I want to get a Flux from getFavourites method and I can use it in getRecommend method.
Or, you can recommend a Flux API ,and I can convert the List<Long> recommendList to Flux<Long> recommendFlux.
To convert a CompletableFuture<List<T>> into a Flux<T> you can use Mono#fromFuture with Mono#flatMapMany:
var future = new CompletableFuture<List<Long>>();
future.completeAsync(() -> List.of(1L, 2L, 3L, 4L, 5L),
CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS));
Flux<Long> flux = Mono.fromFuture(future).flatMapMany(Flux::fromIterable);
flux.subscribe(System.out::println);
List<T> received asynchronously in a callback can be also converted into a Flux<T> without using a CompletableFuture.
You can directly use Mono#create with Mono#flatMapMany:
Flux<Long> flux = Mono.<List<Long>>create(sink -> {
Callback<List<Long>> callback = new Callback<List<Long>>() {
#Override
public void onResult(List<Long> list) {
sink.success(list);
}
#Override
public void onError(Exception e) {
sink.error(e);
}
};
client.call("query", callback);
}).flatMapMany(Flux::fromIterable);
flux.subscribe(System.out::println);
Or simply using Flux#create with multiple emissions in one pass:
Flux<Long> flux = Flux.create(sink -> {
Callback<List<Long>> callback = new Callback<List<Long>>() {
#Override
public void onResult(List<Long> list) {
list.forEach(sink::next);
}
#Override
public void onError(Exception e) {
sink.error(e);
}
};
client.call("query", callback);
});
flux.subscribe(System.out::println);
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();
}
}
}
How can I rewrite this via a Java Lambda?
This question is different from "Lambda Expression and generic method" issue. The issue is because of throwing an Exception.
public Observable<String> getUserInfo() {
return Observable.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
try {
emitter.onNext( getPlayerInfo());
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
When I rewrite this via a normal Lambda, then I get this error:
Incompatible types: Required: Observable <java.lang.String>. Found:
Observable <java.lang.Object>.
This is what I tried:
public Observable<String> getUserInfo() {
return Observable.create( emitter -> {
try {
emitter.onNext(getPlayerInfo();
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
The inference engine of the compiler cannot determine the type parameter of Observable, so you need to explicitly specify it:
return Observable.<String>create( emitter -> {
Type inference fails specifically because of the trailing calls to subscribeOn and observeOn. Another solution is introducing an intermediate method or variable, for example:
private Observable<String> getUserInfo() {
Observable<String> userInfo = Observable.create( emitter -> {
try {
emitter.onNext(getPlayerInfo();
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
return userInfo
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
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);
}
});