I've 2 observables.
1) Sync orders (Completable) 2) Get All orders.
I want to Keep syncing products until I get the desired product from the backend. This is polling the backend 5 times every 5 minutes to retrieve order confirmation.
apiService
.syncOrders()
.repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
#Override
public Observable<Integer> call(final Observable<? extends Void> observable) {
// Retry 5 more times with 5 seconds delay
return observable.delay(5, TimeUnit.SECONDS).zipWith(Observable.range(START_RETRY,
MAX_RETRIES),
new Func2<Void, Integer, Integer>() {
#DebugLog
#Override
public Integer call(Void v,
Integer integer) {
return integer;
}
});
}
}).andThen(apiService.streamOrders().flatMap(new Func1<List<Order>, Observable<Order>>() {
#Override
public Observable<Order> call(List<Order> orderList) {
return Observable.from(orderList);
}
}).filter(new Func1<Order, Boolean>() {
#DebugLog
#Override
public Boolean call(Order order) {
return order.orderRef() == orderId;
}
}).first());
Repeating a Completable that completes normally won't trigger andThen ever. You have to redesign your flow, for example running an Observable.interval with 5 minutes period, flatMap its value into the first completable and attach andThen into that inner flow, for example:
Observable.interval(0, 5, TimeUnit.MINUTES)
.onBackpressureLatest()
.flatMap(tick ->
apiService.syncOrders()
.andThen(apiService.streamOrders().flatMapIterable(list -> list))
.retryWhen(error -> error.delay(5, TimeUnit.SECONDS))
)
.filter(v -> ...)
.subscribe(...);
Related
compositeDisposable += Observable.zip(
someObservable(),
someObservableTwo(), { t1, t2 ->
Pair(first, second)
}
).zipWith(Observable.interval(0, 10, TimeUnit.SECONDS), { t1, t2 ->
t1
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//success
}, {
//error
})
The main objective of this code to execute every 10 seconds. Logic is not wrong but somehow I am missing something. Hope someone helps
What about following solution?
The source observable will emit an event ever 1_000ms. On each event both API endpoints will be called. When the interval source observable emits a new value, while the Observable#combineLatest is subscribed, it will be unsubscribed and called again (switchMap). If you do not want to cancel out the inner stream on each new emit, you woul use flatMap (NOTE: at max 128 concurrent streams are merged by flatMap by default)
interface Api {
Observable<Integer> call1();
Observable<String> call2();
}
static final class ApiImpl implements Api {
#Override
public Observable<Integer> call1() {
return Observable.just(42);
}
#Override
public Observable<String> call2() {
return Observable.just("42");
}
}
#Test
public void interval() {
TestScheduler testScheduler = new TestScheduler();
Api api = new ApiImpl();
TestObserver<Integer> test = Observable.interval(0, 1_000, TimeUnit.MILLISECONDS, testScheduler)
.switchMap(aLong -> Observable.combineLatest(api.call1(), api.call2(), (integer, s) -> 42))
.test();
testScheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS);
test.assertNotComplete().assertValueCount(1)
.assertValues(42);
testScheduler.advanceTimeBy(2_000, TimeUnit.MILLISECONDS);
test.assertNotComplete().assertValueCount(3)
.assertValues(42, 42, 42);
}
Right now I'm implementing some polling logic with RxJava. I'm supposed to poll an endpoint a number of times until it tells me to stop. Additionally, each response comes back with a time that I'm supposed to delay by before polling again. My logic looks something like this right now:
service.pollEndpoint()
.repeatWhen(observable -> observable.delay(5000, TimeUnit.MILLISECONDS))
.takeUntil(Blah::shouldStopPolling);
Right now I have the delay value hardcoded to 5000, but I'd like it to depend on a value in the poll response. I tried using a flatmap that returned Observable.just(pollResponse).repeatWhen(observable -> observable.delay(pollResponse.getDelay(), TimeUnit.MILLISECONDS)), but that didn't seem like the right idea since it messed with the source Observable. I feel like it's something simple that I'm overlooking. Thanks!
As #JohnWowUs mentioned, you need out-of-band communication, but if you subscribe to the sequence more than once, you can use defer to have per-subscriber state:
Observable.defer(() -> {
int[] pollDelay = { 0 };
return service.pollEndpoint()
.doOnNext(response -> pollDelay[0] = response.getDelay())
.repeatWhen(o -> o.flatMap(v -> Observable.timer(pollDelay[0], MILLISECONDS)))
.takeUntil(Blah::shouldStopPolling);
});
You could use the side effect operator doOnNext to update a delay variable and then use that in your repeatWhen
int pollDelay = 5000;
service.pollEndpoint()
.doOnNext(pollResponse -> pollDelay=pollResponse.getDelay())
.repeatWhen(observable -> observable.delay(pollDelay, TimeUnit.MILLISECONDS))
.takeUntil(Blah::shouldStopPolling);
This is the solution I ended up using:
public static Observable<PollResponse> createPollObservable(RetrofitService service, PollResponse response) {
return Blah::shouldStopPolling(response)
? Observable.empty()
: service
.pollEndpoint()
.delaySubscription(getPollDelay(response), TimeUnit.MILLISECONDS)
.concatMap(response1 -> createPollObservable(service, response1)
.startWith(response1)
.takeUntil(Blah::shouldStopPolling)
);
}
It instead uses recursion to always have the latest PollResponse object and also switches to delaySubscription() rather than repeatWhen().
You could abuse retryWhen - but I'm just saying it is possible, not that you should do it:
package com.example.retrywhen;
import com.example.LoggingAction1;
import org.pcollections.PVector;
import org.pcollections.TreePVector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import static com.example.Utils.sleep;
public class RetryWhenDynamicDelayTest {
final static PVector<Integer> delays = TreePVector.<Integer>singleton(500).plus(1_000).plus(2_000);
final static AtomicInteger count = new AtomicInteger(0);
final static Observable<Integer> willCycleThroughTheList = Observable.defer(new Func0<Observable<Integer>>() {
#Override
public Observable<Integer> call() {
return Observable.just(delays.get(count.getAndIncrement() % 3));
}
});
static class ThisIsNotReallyAnException extends Throwable {
final Integer integer;
ThisIsNotReallyAnException(Integer integer) {
this.integer = integer;
}
}
public static void main(String[] args) {
final long now = System.currentTimeMillis();
willCycleThroughTheList.flatMap(new Func1<Integer, Observable<?>>() {
#Override
public Observable<?> call(Integer integer) {
return Observable.error(new ThisIsNotReallyAnException(integer));
}
})
.doOnUnsubscribe(new Action0() {
#Override
public void call() {
System.out.println("Millis since start: " + (System.currentTimeMillis() - now));
}
})
.onErrorResumeNext(new Func1<Throwable, Observable<Integer>>() {
#Override
public Observable<Integer> call(Throwable throwable) {
if (throwable instanceof ThisIsNotReallyAnException) {
ThisIsNotReallyAnException thisIsNotReallyAnException = (ThisIsNotReallyAnException) throwable;
return Observable.just((thisIsNotReallyAnException.integer)).concatWith(Observable.error(throwable));
} else {
return Observable.error(throwable);
}
}
})
.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
#Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(Throwable throwable) {
if (throwable instanceof ThisIsNotReallyAnException) {
ThisIsNotReallyAnException thisIsNotReallyAnException = (ThisIsNotReallyAnException) throwable;
return Observable.timer(thisIsNotReallyAnException.integer, TimeUnit.MILLISECONDS);
} else {
return Observable.error(throwable);
}
}
});
}
})
.subscribeOn(Schedulers.io())
.subscribe(new LoggingAction1<Object>(""));
sleep(10_000);
}
}
Prints:
Millis since start: 75
call (): 500
Millis since start: 590
call (): 1000
Millis since start: 1591
call (): 2000
Millis since start: 3593
call (): 500
Millis since start: 4094
call (): 1000
Millis since start: 5095
call (): 2000
Millis since start: 7096
call (): 500
Millis since start: 7597
call (): 1000
Millis since start: 8598
call (): 2000
service.endpoint()
.flatMap((Function<Response<Void>, SingleSource<?>>) response -> Single.just(response).delaySubscription(getDelayFromResponce(response), TimeUnit.MILLISECONDS))
.repeat()
.subscribe();
I need to do:
Request 2 lists of news from different websites
Combine results from requests
Sort items by date
Get 10 newest news
Save them
Show complete message
For example, I have this two observables:
Observable<RegionalNews> regionalNews;
Observable<NationalNews> nationalNews;
public interface NewsNationalService {
#GET("news/national")
Observable<News> getNationalNews();
}
public interface NewsRegionalService {
#GET("news/regional")
Observable<News> getRegionalNews();
}
You can use zip operator to call 2 requests async and save or process their data on response.
For example.
Below are two Observable
Observable<ResponseOne> responseOneObservable = getRetrofitClient().getDataOne()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Observable<ResponseTwo> responseTwoObservable = getRetrofitClient().getDataTwo()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Using zip operator on above two Observable as below.
Observable<ArrayList<TestData>> testDataObservable = Observable.zip(responseOneObservable, responseTwoObservable, new Func2<ResponseOne, ResponseTwo, ArrayList<TestData>>() {
#Override
public ArrayList<TestData> call(ResponseOne responseOne, ResponseTwo responseTwo) {
ArrayList<TestData> testDataList = new ArrayList();
// process data from response responseOne & responseTwo
return testDataList;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArrayList<TestData>>() {
#Override
public void onNext(ArrayList<TestData> testDataList) {
}
#Override
public void onCompleted() {
Log.d(TAG, "onCompleted" );
// you can show alert here or do something when completed
}
#Override
public void onError(Throwable t) {
Log.d(TAG, "onError Throwable: " + t.toString() );
}
});
If you don't want to do something specific with the combined results, then merge() is enough:
Observable<RegionalNews> regionalNews = ...;
Observable<NationalNews> nationalNews = ...;
Observable
.merge(regionalNews, nationalNews)
.ignoreElements()
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> { /* show alert */ })
.subscribe()
Well it depends, as always. Do you need to process the returned values down the chain, or just save it?
In this implementation I use Single and Completable. You subscribe to the completable and you will get notified when both Singles finished.
#Test
public void name() throws Exception {
TestScheduler testScheduler = new TestScheduler();
Single<Long> request1 = Single.timer(1000, TimeUnit.MILLISECONDS, testScheduler)
.doOnSuccess(aLong -> {
System.out.println("save to db.");
});
Single<Long> request2 = Single.timer(500, TimeUnit.MILLISECONDS, testScheduler)
.doOnSuccess(aLong -> {
System.out.println("save to db.");
});
Completable completable = Single.zip(request1, request2, (aLong, aLong2) -> aLong).toCompletable();
TestObserver<Void> test = completable.test();
testScheduler.advanceTimeBy(1010, TimeUnit.MILLISECONDS);
test.assertComplete();
}
You also can use flatMapCompletable instead of doOnSuccess
#Test
public void name() throws Exception {
TestScheduler testScheduler = new TestScheduler();
Completable request1 = Single.timer(1000, TimeUnit.MILLISECONDS, testScheduler)
.flatMapCompletable(this::saveToDb);
Completable request2 = Single.timer(500, TimeUnit.MILLISECONDS, testScheduler)
.flatMapCompletable(this::saveToDb);
// need to cheat here, becuase completeable does not provide zip
Completable completable = Single.zip(request1.toSingle(() -> 1), request1.toSingle(() -> 1), (aLong, aLong2) -> aLong)
.toCompletable();
TestObserver<Void> test = completable.test();
testScheduler.advanceTimeBy(1010, TimeUnit.MILLISECONDS);
test.assertComplete();
}
private Completable saveToDb(long value) {
return Completable.complete();
}
zip is the way to combine observables. Combining their results is just a consequence.
If you want to wait for both observables to finish (complete), the easiest way is to use zip. You just don't have to use the results of your requests in the combining function. Just use this function as a way to emit something different when both of those calls finish. When this function emits an item:
[...] do something when all requests completed (show alert for example)
For example like this (executing someOtherCall when both of those requests finish):
Observable<Integer> obs1 = ...;
Observable<Long> obs2 = ...;
Observable.zip(obs1, obs2, new Func2<Integer, Long, String>() {
#Override
public String call(Integer integer, Long aLong) {
return "something completely different";
}
}).flatMap(new Func1<String, Observable<Float>>() {
#Override
public Observable<Float> call(String s) {
return performSomeOtherCall();
}
}).subscribe(...);
I have a scenario about using RxJava with interval operator. In fact, I can set interval for Job A. Like that
Observable
.interval(0, 10, TimeUnit.SECONDS)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<Long, List<Bean>>() {
#Override
public List<Bean> call(Long pLong) {
return null;
}
})
.subscribe(new Action1<List<Bean>>() {
#Override
public void call(List<Bean> pO) {
if (pO.size() > 0) setData(pO);
}
});
But I tried to set interval for Job A, Job B, Job C but they have different interval between them.
Job A, Job B, Job C (30 seconds)
Job A -> 5 seconds -> Job B -> 10 seconds -> Job C
Observable
.interval(0, 30, TimeUnit.SECONDS)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Long, Observable<Long>>() {
#Override
public Observable<Long> call(Long pLong) {
//Job A
return Observable.interval(0, 5, TimeUnit.SECONDS);
}
})
.flatMap(new Func1<Long, Observable<Long>>() {
#Override
public Observable<Long> call(Long pLong) {
//Job B
return Observable.interval(0, 10, TimeUnit.SECONDS);
}
})
.subscribe(new Action1<Long>>() {
#Override
public void call(Long pO) {
//Job C
if (pO.size() > 0) setData(pO);
}
});
I tried to use something like this but no luck. I am new to Rx, appreciate any comment about it.
.interval() generates sequence of events, try using .timer() for triggering another job, or .interval().take(1). You will start job B (10 seconds) every 5 seconds (overlapping intervals?)
I've got an EditText view and TextWatcher for it, in onTextChanged method I have to requst server for result with query from EditText field.
In my presenter I use rx for that, but i need to delay search until user's input ends. At this moment i've got this:
service.getData(query)
.delaySubscription(REQUEST_DELAY_FROM_SERVER, TimeUnit.MILLISECONDS, Schedulers.io())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
data-> {
getViewState().showData(data);
},
error -> {
Log.e(this.getClass().getSimpleName(), error.getMessage(), error);
}
);
But delaySubscription does not work as desired. It collects all call and after delay sends every of them. I have to do same as if I had used handler.postDelayed(), when only once request will be send.
Edit 2:
The saple of a presenter in RxJava2
class Presenter {
private PublishSubject<String> queryPublishSubject = PublishSubject.create();
public Presenter() {
queryPublishSubject
.debounce(1000, TimeUnit.MILLISECONDS)
// You might want to skip empty strings
.filter(new Predicate<CharSequence>() {
#Override
public boolean test(CharSequence charSequence) {
return charSequence.length() > 0;
}
})
// Switch to IO thread for network call and flatMap text input to API request
.observeOn(Schedulers.io())
.flatMap(new Function<CharSequence, Observable<...>() {
#Override
public Observable<...> apply(final CharSequence charSequence) {
return ...; // Call API
}
})
// Receive and process response on Main thread (if you need to update UI)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
}
public void onSearchTextChanged(String query) {
queryPublishSubject.onNext(query);
}
}
Edit 1:
The same code in RxJava 1:
class Presenter {
private PublishSubject<String> queryPublishSubject = PublishSubject.crate();
public Presenter() {
queryPublishSubject
.debounce(1000, TimeUnit.MILLISECONDS)
// You might want to skip empty strings
.filter(new Func1<CharSequence, Boolean>() {
#Override
public Boolean call(CharSequence charSequence) {
return charSequence.length() > 0;
}
})
// Switch to IO thread for network call and flatMap text input to API request
.observeOn(Schedulers.io())
.flatMap(new Func1<CharSequence, Observable<...>() {
#Override
public Observable<...> call(final CharSequence charSequence) {
return ... // Call API
}
})
// Receive and process response on Main thread (if you need to update UI)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
}
public void onSearchTextChanged(String query) {
queryPublishSubject.onNext(query);
}
}
Initial answer (with RxBinding and RxJava 1)
The correct answer is to use Debounce, but besides that there are some other tricks you might find useful
textChangeListener = RxTextView
.textChanges(queryEditText)
// as far as I know, subscription to textChanges is allowed from Main thread only
.subscribeOn(AndroidSchedulers.mainThread())
// On subscription Observable emits current text field value. You might not need that
.skip(1)
.debounce(1000, TimeUnit.MILLISECONDS)
// You might want to skip empty strings
.filter(new Func1<CharSequence, Boolean>() {
#Override
public Boolean call(CharSequence charSequence) {
return charSequence.length() > 0;
}
})
// Switch to IO thread for network call and flatMap text input to API request
.observeOn(Schedulers.io())
.flatMap(new Func1<CharSequence, Observable<...>() {
#Override
public Observable<...> call(final CharSequence charSequence) {
return ... // Call API
}
})
// Receive and process response on Main thread (if you need to update UI)
.observeOn(AndroidSchedulers.mainThread())
I have something similar for an address research combining with RxAndroid could give something like that :
RxTextView.textChanges(searchEditText)
.debounce(100, TimeUnit.MILLISECONDS)
.subscribe(....);
The debounce operator will wait in this case that the observable stop to emit for 100ms before emitting the next value.
Try using debounce instead. For eg. code below look for changes in a TextView and do something when there is a change but with a debounce of 100 ms
RxTextView
.textChanges(queryEditText)
.debounce(100, TimeUnit.MILLISECONDS)
.doOnNext(new Action1<CharSequence>() {
#Override
public void call(CharSequence charSequence) {
}
})
.subscribe();