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?)
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);
}
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'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(...);
I'm using RxSearchView to emit out the results of a search query from an API to a recyclerview. However, if one of those query fails, onError() is called(which is expected) but the subscription as a whole is also canceled. Subsequent queries are not executed at all.
How should i modify the code so that the call to onError() is prevented when a query fails and the next incoming queries are executed normally?
Here's a code snippet:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService.getSearchResults(query))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
P.S: I am using switchMap() so that the results of old queries are ignored, if the results of new query has arrived.
You have to handle this error and return an object instead. You can do it, for example, by using onErrorResumeNext operator with apiService.getSearchResults(query) call. What you are going to return - depends on you, you can even return null if you want, but better to create some wrapper which can carry both response status flag and normal response if received.
Something like:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService
.getSearchResults(query)
.onErrorResumeNext(error -> null)
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse != null && searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
Of course, this is naive example with using null, in reality you need to write error handling logic. Better to return wrapper, because if using RxJava 2, then it doesn't support null.
I have a Button from which I create an Observable<OnClickEvent>.
When the button is clicked, I wish to fetch a file from the network, but I run into issues regarding networking and threads.
This example throws android.os.NetworkOnMainThreadException :
Observable<OnClickEvent> networkButtonObservable = ViewObservable.clicks(testNetworkButton);
networkButtonObservable
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
So I try from another thread.
The following throws rx.exceptions.OnErrorNotImplementedException: Observers must subscribe from the main UI thread, but was Thread[RxNewThreadScheduler-1,5,main] :
networkButtonObservable
.subscribeOn(Schedulers.newThread())
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
Ok.. Now I try with a .debounce() at the start :
networkButtonObservable
.debounce(10, TimeUnit.MILLISECONDS)
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
And this succeeds.
Obviously I do not like to add delays to my code, so I am trying to figure out what's going on, thread-wise. Why is the first example not also executing the code inside the .map() in a background thread?
Or what am I missing here?
--- Update
I change my TestAPI to return an Observable, and change the first call to the networkButtonObservable to .flatMap(). This also functions properly. But I still don't know why the original way using .map() should fail.
networkButtonObservable
.flatMap(new Func1<OnClickEvent, Observable<?>>() {
#Override
public Observable<?> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponseObservable();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
I'm not an expert in Android but based on the error messages, I think you need to bounce the value between the main thread and the background thread. Usually, Android examples show you to add a subscribeOn/observeOn pair to your stream processing:
Observable.just(1)
.map(v -> doBackgroundWork())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> {});
but in these cases, the 'source' is usually a cold observable you are in control.
In your question, the source is a hot Observable with specific requirements that you need to subscribe on the main thread, yet you need to do a network call on a background thread and then show the results on the main thread.
In this case, you can use observeOn multiple times:
networkButtonObservable
.subscribeOn(AndroidSchedulers.mainThread()) // just in case
.observeOn(Schedulers.io())
.map(v -> TestAPI.getTestService().fetchTestResponse())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> updateGUI(v));
I think fetchTestResponseObservable has its own subscribeOn or observeOn applied to it so it doesn't throw the network exception.
Also I'd like to mention that using multiple subscribeOn is functionally equivalent to using only one that is closest to the emitting source, but technically it will hog unused threading resources. Using multiple observeOn in a stream, however, has relevance because you can meaningfully 'pipeline' the stream processing between threads with them.