RxJava combining observables without repeating execution - java

Short story:
I have a situation where I have 2 Observables that have a single purpose:
they receive some data
they return modified data
throw an error if the data cannot be processed
They are each in charge of handling different types of data. Additionally I want to do something when both data has been processed.
My current best implementation is as follows, these are my Observables:
Single<BlueData> blueObservable = Single.create(singleSubscriber -> {
if (BlueDataProcessor.isDataValid(myBlueData)) {
singleSubscriber.onSuccess(BlueDataProcessor.process(myBlueData));
}
else {
singleSubscriber.onError(new BlueDataIsInvalidThrow());
}
});
Single<RedData> redObservable = Single.create(singleSubscriber -> {
if (RedDataProcessor.isDataValid(myRedData)) {
singleSubscriber.onSuccess(RedDataProcessor.process(myRedData));
}
else {
singleSubscriber.onError(new RedDataIsInvalidThrowable());
}
});
Single<PurpleData> composedSingle = Single.zip(blueObservable, redObservable,
(blueData, redData) -> PurpleGenerator.combine(blueData, redData));
I also have the following subscriptions:
blueObservable.subscribe(
result -> {
saveBlueProcessStats(result);
},
throwable -> {
logError(throwable);
});
redObservable.subscribe(
result -> {
saveRedProcessStats(result);
},
throwable -> {
logError(throwable);
});
composedSingle.subscribe(
combinedResult -> {
savePurpleProcessStats(combinedResult)
},
throwable -> {
logError(throwable);
});
MY PROBLEM:
The blue & red data is processed twice, because both subscriptions are run again with I subscribe to the combined observable created with Observable.zip().
How can I have this behaviour without running both operations twice?

This is not possible with Single in 1.x because there is no notion of a ConnectableSingle and thus Single.publish. You can achieve the effect via 2.x and the RxJava2Extensions library:
SingleSubject<RedType> red = SingleSubject.create();
SingleSubject<BlueType> blue = SingleSubject.create();
// subscribe interested parties
red.subscribe(...);
blue.subscribe(...);
Single.zip(red, blue, (r, b) -> ...).subscribe(...);
// connect()
blueObservable.subscribe(blue);
redObservable.subscribe(red);

Related

RxJava repeat with delay only part of the flow

I have an Rx flow which performs two actions in sequence whenever a certain event happens:
send an SMS to a given set of numbers - which returns Single<Holster>
save the event on a local DB - which returns Completable
here is my code
private void saveBluetoothAlarm(#NonNull Alarm alarm, int type) {
disposable.add( dbManager.getHolsterDAO().getCurrentHolster()
.map(holsters -> holsters.get(0))
.observeOn(AndroidSchedulers.mainThread())
.flatMap(holster -> sendSmsToAll(holster, alarm.type))
.observeOn(Schedulers.io())
.flatMapCompletable(holster -> {
switch (alarm.type) {
case StatisticsEventType.EXTRACTION:
if (something)
return Completable.complete();
else
return Completable.fromAction(() -> dbManager.getAlarmDAO().insert(alarm))
.andThen(saveAlarmOnServer(holster.getId(), alarm));
case StatisticsEventType.MOVEMENT:
if (somethingMore)
return Completable.complete();
else
return Completable.fromAction(() -> dbManager.getAlarmDAO().insert(alarm))
.andThen(saveAlarmOnServer(holster.getId(), alarm));
}
return Completable.complete();
})
.subscribe(() -> {}, Timber::e)
);
}
everything works, now I need the first action sendSmsToAll(holster, alarm.type) to be repeated a defined amount of times, each delayed by a defined amount of seconds, these settings are defined in my Holster object.
I tried editing to the flatMap() like the following, making sendSmsToAll() return Holster:
.flatMapObservable(holster -> Observable.just(sendSmsToAll(holster, alarm.type))
.repeat(holster.sms_settings.repetitions_count)
.delaySubscription(holster.sms_settings.interval, TimeUnit.SECONDS)
)
but the SMS is sent only once, I even tried a lot of other "combinations" (mostly because I am a noob with RxJava) but nothing works.
Have you tried something like that:
.flatMapObservable(holster -> Observable.zip(Observable.defer(() -> sendSmsToAll(holster, alarm.type)),
Flowable.timer(holster.sms_settings.interval, SECONDS),
(x, y) -> x)
.repeat(holster.sms_settings.repetitions_count))
?

How to remove a source when an error occurs in a zip operator in RxJava2?

I have some sensor inputs from various devices wrapped in Observables.
I want to take the mean value of these sensor values, so a Zip operator seems appropriate. The issue now is that, when connection fails with one of the devices, I do not want the entire thing to just break. Standard behaviour is that if a source gives an error, all sources will terminate as well as the observable from the zip operator itself.
There are two problems with this standard behaviour:
1) When a source gives an error, the other sources will terminate, IE lose connection. The defer operator has to then reastablish this connection which takes some time. I want to transition seamlessly when a device gets removed.
2) Doing .onErrorResumeNext() on the failed source is not an option as this needs a source again. Doing Observable.just(0).repeat() is not acceptable as it is not clean to make special exceptions for 0 values when taking the mean of values.
The behaviour I want to achieve is to seamlessly just stop listening to the observable that gives an error and just go on with the ones that do still work.
Any one got any idea's on how to do this?
EDIT:
I have managed to keep the system working but not in the way I want. It is illustrated with the following code segment:
public FusedHeartRateSensor(HeartRateSensor... devices) {
super("FusedHeartRateSensor", IoTType.TYPE_HEART_RATE_SENSOR, new FusedConnector(), devices);
init();
}
private void init() {
Observable.fromIterable(fusedDevices)
.flatMap(hrs -> hrs.monitorConnection())
.subscribeOn(Schedulers.newThread())
.subscribe(
state -> {
//Als iets verandert met de staat van de geconnecteerde toestellen moet
//de deviceChangeObservable op de hoogte gebracht worden
refreshConnectedDevices();
},
throwable -> {
Log.e(TAG, throwable.getLocalizedMessage());
throwable.printStackTrace();
}
);
}
private void refreshConnectedDevices() {
Log.i(TAG, "Refreshing enabled fused devices");
List<HeartRateSensor> devices = fusedDevices.stream()
.filter(Connectable::isConnected)
.collect(Collectors.toList());
deviceChangeObservable.onNext(devices);
}
private Observable<Integer> getMeanObservable(List<Observable<List<Integer>>> observables) {
System.out.println();
return Observable.zip(observables, objects -> getMean(Arrays.stream(objects).map(o -> (List<Integer>) o).filter(l -> !l.isEmpty()).map(this::getMean).collect(Collectors.toList())));
}
And a bit further when requested to monitor...
#Override
public Observable<Integer> monitorHeartRate() {
ReplaySubject<Integer> resultObservable = ReplaySubject.create();
CompositeDisposable disposable = new CompositeDisposable();
deviceChangeObservable.subscribe(
sensors -> {
Log.i(TAG, "Switched to new sources");
getMeanObservable(sensors.stream()
.map(s -> s.monitorHeartRate()
.buffer(1, TimeUnit.SECONDS))
.collect(Collectors.toList()))
.subscribe(
t -> {
resultObservable.onNext(t);
System.out.println();
},
throwable -> {
throwable.printStackTrace();
System.out.println();
}
);
},
throwable -> {
Log.e(TAG, throwable.getLocalizedMessage());
throwable.printStackTrace();
System.out.println();
}
);
return resultObservable;
}
The problem with this is that, when a sensor fails in the zip operator, all of the other observables in the zip stop. My current solution is to retry the connection and set up a new zip observable only with the observables of devices that are still connected. The problem is that this is not seamless. I also tried this with defer and retry but this has the same result as the code above.

Using reactor's Flux.buffer to batch work only works for single item

I'm trying to use Flux.buffer() to batch up loads from a database.
The use case is that loading records from a DB may be 'bursty', and I'd like to introduce a small buffer to group together loads where possible.
My conceptual approach has been to use some form of processor, publish to it's sink, let that buffer, and then subscribe & filter for the result I want.
I've tried multiple different approaches (different types of processors, creating the filtered Mono in different ways).
Below is where I've gotten so far - largely by stumbling.
Currently, this returns a single result, but subsequent calls are dropped (though I'm unsure of where).
class BatchLoadingRepository {
// I've tried all manner of different processors here. I'm unsure if
// TopicProcessor is the correct one to use.
private val bufferPublisher = TopicProcessor.create<String>()
private val resultsStream = bufferPublisher
.bufferTimeout(50, Duration.ofMillis(50))
// I'm unsure if concatMapIterable is the correct operator here,
// but it seems to work.
// I'm really trying to turn the List<MyEntity>
// into a stream of MyEntity, published on the Flux<>
.concatMapIterable { requestedIds ->
// this is a Spring Data repository. It returns List<MyEntity>
repository.findAllById(requestedIds)
}
// Multiple callers will invoke this method, and then subscribe to receive
// their entity back.
fun findByIdAsync(id: String): Mono<MyEntity> {
// Is there a potential race condition here, caused by a result
// on the resultsStream, before I've subscribed?
return Mono.create<MyEntity> { sink ->
bufferPublisher.sink().next(id)
resultsStream.filter { it.id == id }
.subscribe { next ->
sink.success(next)
}
}
}
}
Hi i was testing your code and i think the best way is to use EmitterProcessor shared. I did a test with emitterProcessor and it seems to work.
Flux<String> fluxi;
EmitterProcessor emitterProcessor;
#Override
public void run(String... args) throws Exception {
emitterProcessor = EmitterProcessor.create();
fluxi = emitterProcessor.share().bufferTimeout(500, Duration.ofMillis(500))
.concatMapIterable(o -> o);
Flux.range(0,1000)
.flatMap(integer -> findByIdAsync(integer.toString()))
.map(s -> {
System.out.println(s);
return s;
}).subscribe();
}
private Mono<String> findByIdAsync(String id) {
return Mono.create(monoSink -> {
fluxi.filter(s -> s == id).subscribe(value -> monoSink.success(value));
emitterProcessor.onNext(id);
});
}

RxJava - making two calls where the first one is conditional

I'm using RxJava 2 to do API's calls.
I have to do a call to cancel the booking of a class.
But I must have to do or not one previous call to get missing information.
It's something like this:
if classId not exists
get classId
then unbook class
else
unbook class
I don't want to repeat the unbook class code.
Here are the code simplified:
FitnessDataService service = RetrofitInstance.getRetrofitInstance().create(FitnessDataService.class);
// if we don't have the aid of class (reserved), we get it from the reserved classes
if (fitClass.getAid() == null) {
service.getReservedClasses(FitHelper.clientId)
.flatMap(reservedClasses ->
{
// get the class ID from reserved classes
...
return service.unbookClass(fitClass.getAid());
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
// success
}, err ->
// error
} else {
service.unbookClass(fitClass.getAid())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
// success
}, err ->
// error
}
As you can see, the service.unbookClass is repeated.
How I can call always this service.unbookClass and only call the service.getReservedClasses if I don't have the class id (fitClass.getAid() == null)
without repeating the code to the second call.
I would suggest separating the actual source of the id into its own separate observable. Maybe something like:
Observable<Long> idObservable;
if (fitClass.getAid() == null) {
idObservable = service.getReservedClasses(FitHelper.clientId)
.map({ reservedClasses ->
/* Get the Id and do stuff with it */
return fitClass.getAid();
});
} else {
idObservable = Observable.just(fitClass.getAid());
}
idObservable.flatMap({ aid -> service.unbookClass(aid) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* TODO */);
Looks like a good use for Maybe and switchIfEmpty. The fromCallable method will return 1 value if the returned value is non-null, and just complete if the item returned is null. switchIfEmpty can be used to provide an alternate (Single or Maybe in this case) if the source did not emit an item. Asuming your retrofit call to unbookClass is returning a single, your code would look something like --
Maybe.fromCallable(() -> fitClass.getAid())
.switchIfEmpty(service.getReservedClasses(FitHelper.clientId)
.flatMap(reservedClasses -> {
// ....
return fitClass.getAid());
}))
.flatMap(id -> service.unbookClass(id))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);

RxJava Combine Sequence Of Requests

The Problem
I have two Apis. Api 1 gives me a List of Items and Api 2 gives me more detailed Information for each of the items I got from Api 1. The way I solved it so far results in bad Performance.
The Question
Efficent and fast solution to this Problem with the help of Retrofit and RxJava.
My Approach
At the Moment my Solution Looks like this:
Step 1: Retrofit executes Single<ArrayList<Information>> from Api 1.
Step 2: I iterate through this Items and make a request for each to Api 2.
Step 3: Retrofit Returns Sequentially executes Single<ExtendedInformation> for
each item
Step 4: After all calls form Api 2 completely executed I create a new Object for all Items combining the Information and Extended Information.
My Code
public void addExtendedInformations(final Information[] informations) {
final ArrayList<InformationDetail> informationDetailArrayList = new ArrayList<>();
final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener = new JSONRequestRatingHelper.RatingRequestListener() {
#Override
public void onDownloadFinished(Information baseInformation, ExtendedInformation extendedInformation) {
informationDetailArrayList.add(new InformationDetail(baseInformation, extendedInformation));
if (informationDetailArrayList.size() >= informations.length){
listener.onAllExtendedInformationLoadedAndCombined(informationDetailArrayList);
}
}
};
for (Information information : informations) {
getExtendedInformation(ratingRequestListener, information);
}
}
public void getRatingsByTitle(final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener, final Information information) {
Single<ExtendedInformation> repos = service.findForTitle(information.title);
disposable.add(repos.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableSingleObserver<ExtendedInformation>() {
#Override
public void onSuccess(ExtendedInformation extendedInformation) {
ratingRequestListener.onDownloadFinished(information, extendedInformation);
}
#Override
public void onError(Throwable e) {
ExtendedInformation extendedInformation = new ExtendedInformation();
ratingRequestListener.onDownloadFinished(extendedInformation, information);
}
}));
}
public interface RatingRequestListener {
void onDownloadFinished(Information information, ExtendedInformation extendedInformation);
}
tl;dr use concatMapEager or flatMap and execute sub-calls asynchronously or on a schedulers.
long story
I'm not an android developer, so my question will be limited to pure RxJava (version 1 and version 2).
If I get the picture right the needed flow is :
some query param
\--> Execute query on API_1 -> list of items
|-> Execute query for item 1 on API_2 -> extended info of item1
|-> Execute query for item 2 on API_2 -> extended info of item1
|-> Execute query for item 3 on API_2 -> extended info of item1
...
\-> Execute query for item n on API_2 -> extended info of item1
\----------------------------------------------------------------------/
|
\--> stream (or list) of extended item info for the query param
Assuming Retrofit generated the clients for
interface Api1 {
#GET("/api1") Observable<List<Item>> items(#Query("param") String param);
}
interface Api2 {
#GET("/api2/{item_id}") Observable<ItemExtended> extendedInfo(#Path("item_id") String item_id);
}
If the order of the item is not important, then it is possible to use flatMap only:
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.flatMap(item -> api2.extendedInfo(item.id()))
.subscribe(...)
But only if the retrofit builder is configured with
Either with the async adapter (calls will be queued in the okhttp internal executor). I personally think this is not a good idea, because you don't have control over this executor.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()
Or with the scheduler based adapter (calls will be scheduled on the RxJava scheduler). It would my preferred option, because you explicitly choose which scheduler is used, it will be most likely the IO scheduler, but you are free to try a different one.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
The reason is that flatMap will subscribe to each observable created by api2.extendedInfo(...) and merge them in the resulting observable. So results will appear in the order they are received.
If the retrofit client is not set to be async or set to run on a scheduler, it is possible to set one :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.flatMap(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
.subscribe(...)
This structure is almost identical to the previous one execpts it indicates locally on which scheduler each api2.extendedInfo is supposed to run.
It is possible to tune the maxConcurrency parameter of flatMap to control how many request you want to perform at the same time. Although I'd be cautious on this one, you don't want run all queries at the same time. Usually the default maxConcurrency is good enough (128).
Now if order of the original query matter. concatMap is usually the operator that does the same thing as flatMap in order but sequentially, which turns out to be slow if the code need to wait for all sub-queries to be performed. The solution though is one step further with concatMapEager, this one will subscribe to observable in order, and buffer the results as needed.
Assuming retrofit clients are async or ran on a specific scheduler :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.concatMapEager(item -> api2.extendedInfo(item.id()))
.subscribe(...)
Or if the scheduler has to be set locally :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.concatMapEager(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
.subscribe(...)
It is also possible to tune the concurrency in this operator.
Additionally if the Api is returning Flowable, it is possible to use .parallel that is still in beta at this time in RxJava 2.1.7. But then results are not in order and I don't know a way (yet?) to order them without sorting after.
api.items(queryParam) // Flowable<Item>
.parallel(10)
.runOn(Schedulers.io())
.map(item -> api2.extendedInfo(item.id()))
.sequential(); // Flowable<ItemExtended>
the flatMap operator is designed to cater to these types of workflows.
i'll outline the broad strokes with a simple five step example. hopefully you can easily reconstruct the same principles in your code:
#Test fun flatMapExample() {
// (1) constructing a fake stream that emits a list of values
Observable.just(listOf(1, 2, 3, 4, 5))
// (2) convert our List emission into a stream of its constituent values
.flatMap { numbers -> Observable.fromIterable(numbers) }
// (3) subsequently convert each individual value emission into an Observable of some
// newly calculated type
.flatMap { number ->
when(number) {
1 -> Observable.just("A1")
2 -> Observable.just("B2")
3 -> Observable.just("C3")
4 -> Observable.just("D4")
5 -> Observable.just("E5")
else -> throw RuntimeException("Unexpected value for number [$number]")
}
}
// (4) collect all the final emissions into a list
.toList()
.subscribeBy(
onSuccess = {
// (5) handle all the combined results (in list form) here
println("## onNext($it)")
},
onError = { error ->
println("## onError(${error.message})")
}
)
}
(incidentally, if the order of the emissions matter, look at using concatMap instead).
i hope that helps.
Check below it's working.
Say you have multiple network calls you need to make–cals to get Github user information and Github user events for example.
And you want to wait for each to return before updating the UI. RxJava can help you here.
Let’s first define our Retrofit object to access Github’s API, then setup two observables for the two network requests call.
Retrofit repo = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Observable<JsonObject> userObservable = repo
.create(GitHubUser.class)
.getUser(loginName)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<JsonArray> eventsObservable = repo
.create(GitHubEvents.class)
.listEvents(loginName)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Used Interface for it like below:
public interface GitHubUser {
#GET("users/{user}")
Observable<JsonObject> getUser(#Path("user") String user);
}
public interface GitHubEvents {
#GET("users/{user}/events")
Observable<JsonArray> listEvents(#Path("user") String user);
}
After we use RxJava’s zip method to combine our two Observables and wait for them to complete before creating a new Observable.
Observable<UserAndEvents> combined = Observable.zip(userObservable, eventsObservable, new Func2<JsonObject, JsonArray, UserAndEvents>() {
#Override
public UserAndEvents call(JsonObject jsonObject, JsonArray jsonElements) {
return new UserAndEvents(jsonObject, jsonElements);
}
});
Finally let’s call the subscribe method on our new combined Observable:
combined.subscribe(new Subscriber<UserAndEvents>() {
...
#Override
public void onNext(UserAndEvents o) {
// You can access the results of the
// two observabes via the POJO now
}
});
No more waiting in threads etc for network calls to finish. RxJava has done all that for you in zip().
hope my answer helps you.
I solved a similar problem with RxJava2. Execution of requests for Api 2 in parallel slightly speeds up the work.
private InformationRepository informationRepository;
//init....
public Single<List<FullInformation>> getFullInformation() {
return informationRepository.getInformationList()
.subscribeOn(Schedulers.io())//I usually write subscribeOn() in the repository, here - for clarity
.flatMapObservable(Observable::fromIterable)
.flatMapSingle(this::getFullInformation)
.collect(ArrayList::new, List::add);
}
private Single<FullInformation> getFullInformation(Information information) {
return informationRepository.getExtendedInformation(information)
.map(extendedInformation -> new FullInformation(information, extendedInformation))
.subscribeOn(Schedulers.io());//execute requests in parallel
}
InformationRepository - just interface. Its implementation is not interesting for us.
public interface InformationRepository {
Single<List<Information>> getInformationList();
Single<ExtendedInformation> getExtendedInformation(Information information);
}
FullInformation - container for result.
public class FullInformation {
private Information information;
private ExtendedInformation extendedInformation;
public FullInformation(Information information, ExtendedInformation extendedInformation) {
this.information = information;
this.extendedInformation = extendedInformation;
}
}
Try using Observable.zip() operator. It will wait until both Api calls are finished before continuing the stream. Then you can insert some logic by calling flatMap() afterwards.
http://reactivex.io/documentation/operators/zip.html

Categories