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))
?
Related
I am new to Java Rx, I don't know if that is a valid question or not.
I have function
public Single<PayResponse> pay(PayRequest apiRequest) {
return client.initiatePayment(apiRequest)
.doOnSuccess(initiatePaymentResponse -> {
System.out.println("first");
client.confirmPayment(initiatePaymentResponse.getPaymentId())
.doOnSuccess(confirmPaymentResponse -> {System.out.println("second");doConfirmationLogic(confirmPaymentResponse ))}
.doOnError(ex -> {System.out.println("thirs");ex.printStackTrace();logError(ex);});
})
.doOnError(ex -> {ex.printStackTrace();logError(ex);});
}
after executing this method i can find first was printed twice but neither second nor third was printed
It is odd behaviour for me, because i expect to find first and second or third.
Any idea ?
In order to start receiving the emitted value(s) from an observable (like a Single<T>), you must subscribe() to it first.
You are probably only subscribing to the Single returned by pay twice somewhere else, and that's why you see first printed two times. In the code you show, I can see that are not subscribing to any of the observable there, so nothing will happen afterwards.
If you want to chain observables, the most common choice would be to use the flatMap operator (there are other options as well).
In your case, it would look similar to this:
public Single<PayResponse> pay(PayRequest apiRequest) {
return client.initiatePayment(apiRequest)
.flatMap(initiatePaymentResponse -> {
System.out.println("first");
return client.confirmPayment(initiatePaymentResponse.getPaymentId();
})
.flatMap(confirmPaymentResponse -> {
System.out.println("second");
return doConfirmationLogic(confirmPaymentResponse);
})
.doOnSuccess(confirmationLogicResponse -> System.out.println("third"))
.doOnError(ex -> {
ex.printStackTrace();
logError(ex);
});
}
Then, you subscribe to the single returned by pay somewhere else like this:
...
pay(apiRequest)
.subscribe(onSuccesValue -> {
// The whole chain was successful and this is the value returned
// by the last observable in the chain (doConfirmationLogic in your case)
}, onError {
// There was an error at some point during the chain
}
...
I am supposing that all the methods initiatePayment, confirmPayment, doConfirmationLogic return Singles and that doConfirmationLogic ends up returning a Single<PayResponse>. If that's not the case, you will need to make some small changes, but you get the general idea of how chaining observables work.
Here is some RxJava code that I want to test:
public void triggerCancelOrderJob() {
couchConnector()
.findAbandonedOpenOrders()
.flatMap(results -> results.rows())
.flatMap(
row ->
Observable.just(row)
.subscribeOn(Schedulers.io())
.map(
s -> return s.value())
.flatMap(
orderId -> {
return RxReactiveStreams.toObservable(
serviceTokenCache
.get(OrderApiConstants.SERVICE_TOKEN_CACHE_KEY)
.flatMap(
issueToken -> {
return cancelOrderApiConnector()
.invokeAPI(
RequestInputModel.builder().build(),
RequestInputModel.RequestBodyModel.builder().build());
}));
}))
.subscribe(//additional code)
So what's happening is that I run an async CB query, get an Observable< AsyncN1qlQueryResult >, then for each row I call call two external services one after the another (first call to the serviceTokenCache and second call to the cancelOrderApiConnector). Each row runs in a separate IO thread.
Note: serviceTokenCache.get() and cancelOrderApiConnector().invokeAPI() return a Mono respectively.
I cannot figure out how to test this code. What all components need to be tested? Since each row will run in its separate thread, I cannot wrap my head around how to test such asynchronous code.
I have a remote call(retrofit) - which I converted into an Observable. Let's call it Observable Y.
Now, I also have a certain code that looks for geo location with GPS and NETWORK providers. I have a Timer there, that basically limits the time that the geo search can be performed for. Let's call it Task X. I want to convert it into an Observable X.
Then, I want to have a subcription, that will perform Observable X(that is, find location), once it will return a Location, I will "analyze" in a certain way, and then I will either pass that Location into the Observable Y(the retrofit call), or simply quit(if that "raw" location will be enough in my case)
At ALL time, I want to be able to interrupt all that "process". From what I gather, I can achieve that by simply unsubscribing the subscription, right?
and then next time just subscribe this subscription once again in the future.
Questions:
1. Can all of that be implemented via RxJava/RxAndroid ?
2. Does it even make sense implementing it with Rx ? or is there a more efficient way?
3. How is it done with Rx?
(More specifically : (a) How do I convert task Y into an Observable Y?
(b) How do I perform them in sequence with only one subscription?)
1- It can be implemented via RxJava
2- This is your best option so far
3-
3-a Observable.fromCallable() does the trick
3-b flatmap operator is used to chain observable calls
you can proceed like this:
private Location searchForLocation() {
// of course you will return not null location
return null;
}
// your task X
//mock your location fetching
private Observable<Location> getLocationObservableX() {
return Observable.fromCallable(() -> searchForLocation());
}
//your task Y
//replace CustomData with simple String
//just to mock your asynchronous retrofit call
private Observable<List<String>> getRetrofitCallObservableY(String param){
return Observable.just(new ArrayList<String>());
}
//subscribe
private void initialize() {
getLocationObservableX()
.filter(location -> {
//place your if else here
//condition
//don't continue tu retrofit
boolean condition = false;
if (condition) {
//process
//quit and pass that Location in Broadcas
//you shall return false if you don't want to continue
return false;
}
return true;
})
//filter operation does not continue here if you return false
.flatMap(location -> getRetrofitCallObservableY("param"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
//do what you want with response
});
}
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(...);
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);