RxJava - making two calls where the first one is conditional - java

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(...);

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))
?

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);
});
}

Using initial value of observable in subscription along with the mapped one

Take a look at the following code snippet:
vertx.eventBus().consumer<JsonObject>(CREATEMEETING_SERVICE_ID).toObservable()
.map { objectMapper.readValue(it.body().getJsonObject("body").encode(), Meeting::class.java) }
.flatMap (meetingService::createMeeting)
.subscribe(
{ Json.encodePrettily(it) },
{ throw it }
)
I need to call message.reply method from the initial observable in onNext method of subscribe. One way of doing this is to use Pairs of rather than Meeting alone what makes this ugly. Is there any other option to make this work not having to use Pairs?
I know one more way which is like this:
vertx.eventBus().consumer<JsonObject>(CREATEMEETING_SERVICE_ID).toObservable()
.subscribe {
message ->
Observable.just(objectMapper.readValue(message.body().getJsonObject("body").encode(), Meeting::class.java))
.flatMap(meetingService::createMeeting)
.subscribe(
{ message.reply(Json.encodePrettily(it)) },
{ throw it }
)
}
But it also looks wrong.
Maybe it also explains the problem.

RxJava combining observables without repeating execution

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);

Understanding How RxJava Operator Works

I'm having a hard time understanding some component in RxJava and how they work.
I have these code based on how to implement Repository Pattern with RxJava:
private Observable<String> getData(boolean refresh) {
Observable<String> a = Observable.concat(getCache(), getNetwork());
if(!refresh) {
a.first(s -> s.equals("cache"));
}
return a;
}
private Observable<String> getCache() {
return Observable.just("cache");
}
private Observable<String> getNetwork() {
return Observable.just("network");
}
And I called the function:
getData(false).subscribe(s -> Log.d("Not Refresh", s));
getData(true).subscribe(s -> Log.d("Refresh", s));
// Both of them print this:
// cache
// network
Which doesn't right because I applied first() function when refresh = true.
Then, I thought maybe first() operator didn't reflect to the original object; so I re-assign it back.
if(!refresh) {
a = a.first(s -> s.equals("cache"));
}
Then it worked like I wanted to and print these lines:
// Not Refresh cache
// Refresh cache
// Refresh network
I moved on and learn on another thing, RxBus. My code:
private ReplaySubject<Object> subject = ReplaySubject.create();
private <T> Observable<T> subscribe(Class<T> desiredClass) {
return subject
.filter(item -> item.getClass().equals(desiredClass)) // Note this line
.map(item -> (T) item);
}
private <T> void post(T item) {
subject.onNext(item); // This one too
}
Called the functions with these:
sub(String.class).subscribe(s -> Log.d("Sub String", s));
sub(Integer.class).subscribe(s -> Log.d("Sub Integer", s + ""));
post("String A");
post(5);
// Result:
// Sub String A
// Sub Integer 5
When I call sub(), it applied filter() and map() operators and return it. In my understanding the original subject is not changed, then why does invoking subject.onNext() also invoke the modified object returned in the sub()?
Does it have anything to do with Subject? Or my understanding of RxJava is completely wrong here?
You miss one thing: Subject is both an Observable and Observer.
Subject will act like an Observable when you call Observable's methods (e.g., filter, map), and it will return a new Observable without changing the original one.
However, Subject will act like an Observer when you call Observer's methods (e.g., onNext, onCompleted, onError), and you will see side effects of these methods.

Categories