I'm a newbie in RxJava and faced an issue as follows:
I have two Completable objects to store some data. I'd like to trigger the first one and later on start the second one only after the first one has finished with success. The call to the second Completable should be blocked until the first one has finished with success. Also, if the first one has finished with error, the other one should be also skipped.
Looking through documentation and other SO questions it seems that concatWith or andThen should work for me. But in both manual test and unit test I can see that the second completable is triggered in parallel to the first one :/
First completable
public Completable doA() {
Log.d(TAG, "class call");
return db.countRows()
.doOnSuccess(integer -> {
Log.d(TAG, "found rows: "+integer);
})
.doOnError(Throwable::printStackTrace)
.flatMapCompletable(this::customAction);
}
private Completable customAction(final int count) {
Log.d(TAG, "count: "+count);
if (count > 0) {
Log.d(TAG, "no rows, skip");
return Completable.complete();
}
final User user = ...
return db.save(user); // return Completable
}
Second Completable
public Completable doB() {
Log.d(TAG, "call to B");
// ...
}
Attempt to invoke B after A
public Completable someMethod() {
Log.d(TAG, "someMethod");
return doA()
.andThen(doB());
// this also doesn't work
//.concatWith(doB());
}
The subscription
someMethod()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> {
Log.d(TAG, "complete");
// ...
})
.doOnError(throwable -> {
Log.d("Main", "error "+throwable.getMessage());
// ...
})
.subscribe();
When I run my app and check logs I can see:
D/Some method: some method
D/DoA: class call
D/DoB: class call // <- why here?
D/DoA: found rows: 0
D/DoA: count: 0
Also the following unit test fails:
#Test
public void test() {
when(doa.doA()).thenReturn(Completable.error(new Exception("test")));
observe(); // subscription with TestObserver
verify(dob, never()).doB(); // fails with NeverWantedButInvoked
}
What am I missing?
Because you called doB(). Let me rewrite your flow:
public Completable someMethod() {
Log.d(TAG, "someMethod");
// doA() inlined
LOG.d("class call");
Completable a = ...
// doB() inlined
Log.d("class call");
Completable b = ...
return a.andThen(b);
}
You can use andThen() or concatWith() operator.
Returns a Completable that first runs this Completable and then the other completable.
andThen()
firstCompletable
.andThen(secondCompletable)
In general, this operator is a "replacement" for a flatMap on Completable:
Completable andThen(CompletableSource next)
<T> Maybe<T> andThen(MaybeSource<T> next)
<T> Observable<T> andThen(ObservableSource<T> next)
<T> Flowable<T> andThen(Publisher<T> next)
<T> Single<T> andThen(SingleSource<T> next)
concatWith:
firstCompletable
.concatWith(secondCompletable)
Related
I have two Subjects, one subscribes to the other for updates.
Subject<Integer> subject = new Subject<>() {
#Override
public boolean hasObservers() {
return false;
}
#Override
public boolean hasThrowable() {
return false;
}
#Override
public boolean hasComplete() {
return false;
}
#Override
public Throwable getThrowable() {
return null;
}
#Override
protected void subscribeActual(Observer<? super InitialAPIResponse> observer) {
}
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Integer result) {
Log.d(TAG, "onNext: " + apiResponse);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
subject.doOnNext(result -> Log.d("Subject", "accept: " + result));
observableSubject
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(subject);
observableSubject.onNext(1);
observableSubject.onComplete();
When the onNext() is called, the Consumer's accept() provided in doOnNext() is not. Even though according to the documentation
Observable.doOnNext()
Modifies the source ObservableSource so that it invokes an action when it calls onNext.
Scheduler:
doOnNext does not operate by default on a particular Scheduler
onNext
the action to invoke when the source ObservableSource calls onNext
return the source ObservableSource with the side-effecting behavior applied
From what I understand from the documentation the observable should call the Consumer in doOnNext().
I'm learning RxJava so maybe I'm doing something wrong here...
There are two problems:
1.
subject.doOnNext(result -> Log.d("Subject", "accept: " + result));
In above code, the result of doOnNext is not subscribed. doOnNext does not subscribe to upstream on its own, just as many other operators. Change to this, for example:
subject.doOnNext(result -> Log.d("Subject", "accept: " + result)).subscribe();
2.
observableSubject
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(subject);
observableSubject.onNext(1);
observableSubject.onComplete();
In above code, onComplete is called immediately after .onNext. This can cause timing issues when emitting the values.
Change above code to either
observableSubject
.subscribe(subject); // subscribe on the same thread so that everything happens sequentially.
observableSubject.onNext(1);
observableSubject.onComplete();
or
Subject<Integer> observableSubject = BehaviorSubject.create();
observableSubject
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(subject);
observableSubject.onNext(1);
// observableSubject.onComplete(); // don't call onComplete/
I know that if the Observable emits a piece of data, it resubscribes, and if the Observable emits an onError notification, it passes that notification to the observer and terminates. The problem is that if I emit a Obervable.just(1,2),but it won't be accept by the observer.So what's the usage of it?Dose it just tell it to resubscribes,and it's not important what data i emit?
Observable.just(1, "2", 3)
.cast(Integer.class)
.retryWhen(new Function<Observable<Throwable>, ObservableSource<Integer>>() {
#Override
public ObservableSource<Integer> apply(Observable<Throwable> throwableObservable) throws Exception {
return Observable.just(4,5);
}
})
.subscribe(new Consumer<Integer>() {
#Override
public void accept(Integer integer) throws Exception {
Log.i(TAG, "retryWhen重试数据"+integer);
}
});
and the log is
retryWhen重试数据1
retryWhen重试数据1
so Observable.just(4,5) is gone?
You can check out this example from the documentation to better understand how the retryWhen supposed to work (source: http://reactivex.io/RxJava/javadoc/io/reactivex/Observable.html#retryWhen-io.reactivex.functions.Function-):
Observable.create((ObservableEmitter<? super String> s) -> {
System.out.println("subscribing");
s.onError(new RuntimeException("always fails"));
}).retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> {
System.out.println("delay retry by " + i + " second(s)");
return Observable.timer(i, TimeUnit.SECONDS);
});
}).blockingForEach(System.out::println);
Output is:
subscribing
delay retry by 1 second(s)
subscribing
delay retry by 2 second(s)
subscribing
delay retry by 3 second(s)
subscribing
There are two issues which I am currently facing.
1) As soon as the line RetrofitProvider.getInstance().getCurrentWeather(.....) is called the network call is being done. How can it be deferred till the observer is connected to it.
2) Once weatherInfoPublisher.onComplete() is called, the next time I call onComplete on this object the new observer's onNext is not getting called.
public Observable<LinkedList<WeatherInfo>> getWeatherData(final String payload, final TempUnit tempUnit) {
PublishSubject weatherInfoPublisher = PublishSubject.create();
RetrofitProvider.getInstance().getCurrentWeather(payload + ",us", translateTempUnit(tempUnit))
.flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String todayResponse) throws Exception {
Log.d(TAG, "Received today weather: " + todayResponse);
parseTodayData(todayResponse, weatherDataList);
return RetrofitProvider.getInstance().getForecastWeather(
payload + ",us", translateTempUnit(tempUnit), FORECAST_DAYS);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<String>() {
#Override
public void onNext(String futureResponse) {
Log.d(TAG, "Received future weather: " + futureResponse);
parseFutureData(futureResponse, weatherDataList);
weatherInfoPublisher.onNext(weatherDataList);
weatherInfoPublisher.onComplete();
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "The error is, " + e.getMessage());
}
#Override
public void onComplete() {
}
});
return weatherInfoPublisher;
}
This is a singleton class and the entire implementation has been provided in here in Github Link.
How can it be deferred till the observer is connected to it.
Do not subscribe to that observable in this method. Instead return that observable to the client. As soon as the observable is subscribed - a request would be performed.
the next time I call onComplete on this object the new observer's onNext is not getting called.
See reactive stream specs: if a stream completes - it can never be continued, that's a terminal event.
I encountered a strange situation. I'm fiddling with CompletableFuture and when running the following code I have unexpected results:
public static void main(String[] args) {
CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<Object>>>>>> completableFutureCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("first");
return CompletableFuture.supplyAsync(() -> {
System.out.println("second");
return CompletableFuture.supplyAsync(() -> {
System.out.println("third");
return CompletableFuture.supplyAsync(() -> {
System.out.println("fourth");
return CompletableFuture.supplyAsync(() -> {
System.out.println("fifth");
return CompletableFuture.completedFuture(null);
});
});
});
});
});
completableFutureCompletableFuture.get();
}
No exception is thrown (even when using exceptionally) and what I see is that the console output is
first
second
third // appears sometimes
Now, obviously this code has no real production value but this is a representation of a case where your code has an unknown number of nestings where each, or some of them, create CompleteableFutures which won't be executed.
Any explanation (and example on how to fix) would be greatly appreciated
The reason why this doesn't work is because in your simple test the VM exits before all tasks are completed.
When you call completableFutureCompletableFuture.get() only the first nesting of the futures is guaranteed to have finished. The VM exits, and all threads get killed.
In other words, the first nested future could still be "uncompleted" as its thread might still be busy. However, when you try to get its result with get it will of course wait until it completed and it will work as expected. Just try:
completableFutureCompletableFuture.get().get().get().get().get()
... then you force all futures to have completed and everything works as expected.
It happens because your CompletableFuture are executed asynchronously but your program terminates before the fifth call happens (I assume you ran it in a single main and returned just after creating your futures).
As you may not know how many Future are stacked in your Future (due to type erasure). You may want to perform a recursive .get().
See :
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<?> futures = getFutures();
recursiveGet(futures);
System.out.println("finished");
}
public static CompletableFuture<?> getFutures() {
CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<CompletableFuture<Object>>>>>> compositeCompletable = CompletableFuture.supplyAsync(() -> {
System.out.println("first");
return CompletableFuture.supplyAsync(() -> {
System.out.println("second");
return CompletableFuture.supplyAsync(() -> {
System.out.println("third");
return CompletableFuture.supplyAsync(() -> {
System.out.println("fourth");
return CompletableFuture.supplyAsync(() -> {
System.out.println("fifth");
return CompletableFuture.completedFuture(null);
});
});
});
});
});
return compositeCompletable;
}
public static void recursiveGet(Future<?> future) throws InterruptedException, ExecutionException{
Object result = future.get();
if(result instanceof Future){
recursiveGet((Future<?>) result);
}
}
which returns
first
second
third
fourth
fifth
finished
Just tested this and it works. I think the reason why is not working for you is because you run in in a main method and you did not wait to complete. I did a Thread.sleep(1000) after your code and it worked. The best way would be to wai for termination: completableFutureCompletableFuture.get().get().get().get().get()
Observable.create(new Observable.OnSubscribe<Integer>() {
#Override
public void call(Subscriber<? super Integer> subscriber) {
subscriber.onStart();
subscriber.onNext(1);
subscriber.onCompleted();
}
}).delaySubscription(5, TimeUnit.SECONDS).subscribe(new Subscriber<Integer>() {
#Override
public void onCompleted() {
Log.e("TAG", String.format("(%s) - onCompleted", System.currentTimeMillis()));
}
#Override
public void onError(Throwable e) {
Log.e("TAG", String.format("(%s) - onError", System.currentTimeMillis()), e);
}
#Override
public void onNext(Integer integer) {
Log.e("TAG", String.format("(%s) - onNext: %s", System.currentTimeMillis(), integer));
}
#Override
public void onStart() {
super.onStart();
Log.e("TAG", String.format("(%s) - onStart", System.currentTimeMillis()));
}
});
output:
(1485004553817) - onStart
(1485004558818) - onNext: 1
(1485004558819) - onCompleted
why onStart event not waiting to delaySubscription and calling soon ?
i want aware when call method called
Documentation says -
onStart -
This method is invoked when the Subscriber and Observable have been connected but the Observable has not yet begun to emit items or send notifications to the Subscriber.
delaySubscription:
Returns an Observable that delays the subscription to the source Observable by a given amount of time.
onNext is invoked only when the subscription is achieved. onStart is called the moment a connection is established. Thus, it works as expected according to the definition.
You can try commenting the code subscriber.onStart(); and execute the same again to notice that onStart is still called at the beginning. The intentional execution did not really invoke the said method because this was executed not on the real subscriber we created, but the one which was a result of delaySubscription (of type OnSubscribeDelaySubscription).
Below is a snippet which can probably help you achieve what you're looking for:
public static void main(String[] args) throws UnsupportedEncodingException, IOException {
Observable.timer(5, TimeUnit.SECONDS).flatMap(val -> {
System.out.println("Initialize");
return Observable.create(subscriber -> {
System.out.println("onsubscribe");
doMyAsyncStuff(subscriber);
});
}).subscribe(val -> System.out.println(val));
Observable.timer(10, TimeUnit.SECONDS).toBlocking().first();
}
We initialize a timer, once timer is executed, we perform some task in flatMap which should be the same as what you earlier did with onStart. Once that task is executed, we emit a Observable which emits all the elements that you could have consumed earlier with onNext calls.