Subscribing to 2 observables but only subscribe to the second one if a condition is true for the first one - rx-java2

RxJava 2
I have the following where I am subscribing to 2 observables it works ok. I don't think its the best way.
I only want to subscribe to the second one getSalesInfo if the first one getProductDetails meets a condition. This is just a sample of what I am trying to do. If the condition is not met then nothing more will happen.
fun main(args: Array<String>) {
getProductDetails()
.subscribeBy { productDetails ->
if (productDetails.productID == 1234) {
getSalesInfo().subscribeBy {
getSalesInfo()
.subscribeBy { saleInfo ->
println(saleInfo.salesReference)
}
}
}
}
}
fun getProductDetails(): Observable<ProductDetails> =
Observable.just(ProductDetails(1234, "Product One"))
fun getSalesInfo(): Observable<SaleInfo> =
Observable.just(SaleInfo("Sales Reference 1"))
data class ProductDetails(val productID: Int,
val productDescription: String)
data class SaleInfo(val salesReference: String)
Another alternative I have found is using flatmap that will return the second SaleInfo observable. I have to return a empty Observable in the else condition which doesn't look right. Is there a better way?
getProductDetails()
.flatMap { productDetails: ProductDetails ->
if (productDetails.productID == 1234) {
getSalesInfo()
}
else {
Observable.empty()
}
}
.subscribeBy { saleInfo ->
println("Using flatmap ${saleInfo.salesReference}")
}
Many thanks for any suggestions

I would suggest that your first method is better. It describes exactly what you want it to do, and has the benefit that if you wanted to take some action when productID != 1234, you can have a different callback for the RX action that is taken. While they do essentially the same thing, there is probably a slight overhead for the second one due to the flatMap, and it also reduces flexibility.

Related

How to preform operation which returns single for each element of list returned by observable and return it as a list?

The question is quite complicated but in code seems to look simpler (I hope)
I have a functions like bellow
fun trackFoos(): Observable<List<Foo>> {/*....*/}
fun getBarForFoo(fooState: Foo.State): Single<Bar>
And I was having some code which is bound to other parts of apps so I cannot change it too much.
Observable.combineLatest(
trackFoos(),
Observable.interval(1, TimeUnit.SECONDS),
BiFunction() {foos: List<Foo>, _:Long -> foos}
).subscribe { foos -> /*....*/ }
And now I need to pass also object of Bar for each foo object using this getBarForFoo
So I need to write trackPairOfFooAndBar() using trackFoos() and getBarForFoo() to use it as below:
Observable.combineLatest(
trackPairOfFooAndBar(),
Observable.interval(1, TimeUnit.SECONDS),
BiFunction() {fooBarList: List<Pair<Foo,Bar>>, _:Long -> fooBarList}
).subscribe { fooBarList-> /*....*/ }
I saw this answer: https://stackoverflow.com/a/47676208/1461568 but there is assumption that second call is observable (I have Single)
So how to do it?
You can do it like this:
Transform trackFoos() from Observable<List<Foo>> to Observable<Foo>
For each Foo do getBarForFoo() and map result so it really returns Pair<Foo, Bar>
(Optional) If you need Single<List<Pair<Foo,Bar>>> you can just call toList()
(Optional) If you really need Observable then you can just call toObservable()
In code:
fun trackPairOfFooAndBar() : Observable<Pair<Foo, Bar>> {
return trackFoos()
.flatMapIterable { it } // or flatMap {list -> Observable.fromIterable(list)}
.flatMapSingle { foo -> getBarForFoo(foo.state).map { foo to it } }
// optional, depending what is the final signature that you want
//.toList().toObservable
}

How can I get a detailed array from another observable of array

I am beginner in RxSwift in general trying to chain two different operation with my REST API to get all detailed products.
I wrapped some REST API to return RxSwift Observable, one return a list of product and other the product detail.
class API {
func listProduct() -> Observable<[Product]> { ... }
func detailProdcut(code: Int) -> Observable<[ProductDetail]> { ... }
}
Now I want to get the product detail from a product list, how can I do this in Rx way ? I'm trying to do something like
API.init().listProduct()
.flapMap { products -> <Product> in return products[0] }
.map { product in API.init(product.code) }
.merge()
.toArray
But isn't work, and I very confused about how to transform one list of products code into an array of products detail
let api = API()
let productDetails = api.listProducts()
.flatMap { products in
let productsObservable = Observable.from(products)
let productDetails = productsObservable.flatMap { api.detail(product($0.code) }
return productDetails.toArray()
}
What is going on here :
flatMap has specialized type [Product] -> Observable<[ProductDetails]>. How? Read on.
Observable.from takes a swift array and transforms it into an observable. It will emit next event for each of the element of the array. So we now have Observable<Product> in productsObservable.
the call to flatMap on productObservable will create an Observable<ProductDetail> for each next event it sends. We now have an observable that will send as many next event as their are products in the result of listProducts.
We use toArray to transform this observable to something that will emit only one next event, an array aggregating all the results from productDetails.

RxSwift: Nested Queries and ReplaySubject

I have to fetch three types of data (AType, BType, CType) using three separate API requests. The objects returned by the APIs are related by one-to-many:
1 AType object is parent of N BType objects
1 BType object is parent of P CType objects)
I'm using the following three functions to fetch each type:
func get_A_objects() -> Observable<AType> { /* code here */ }
func get_B_objects(a_parentid:Int) -> Observable<BType> { /* code here */}
func get_C_objects(b_parentid:Int) -> Observable<CType> { /* code here */}
and to avoid nested subscriptions, these three functions are chained using flatMap:
func getAll() -> Observable<CType> {
return self.get_A_objects()
.flatMap { (aa:AType) in return get_B_objects(aa.id) }
.flatMap { (bb:BType) in return get_C_objects(bb.id) }
}
func setup() {
self.getAll().subscribeNext { _ in
print ("One more item fetched")
}
}
The above code works fine, when there are M objects of AType, I could see the text "One more item fetched" printed MxNxP times.
I'd like to setup the getAll() function to deliver status updates throughout the chain using ReplaySubject<String>. My initial thought is to write something like:
func getAll() -> ReplaySubject<String> {
let msg = ReplaySubject<String>.createUnbounded()
self.get_A_objects().doOnNext { aobj in msg.onNext ("Fetching A \(aobj)") }
.flatMap { (aa:AType) in
return get_B_objects(aa.id).doOnNext { bobj in msg.onNext ("Fetching B \(bobj)") }
}
.flatMap { (bb:BType) in
return get_C_objects(bb.id).doOnNext { cobj in msg.onNext ("Fetching C \(cobj)") }
}
return msg
}
but this attempt failed, i.e., the following print() does not print anything.
getAll().subscribeNext {
print ($0)
}
How should I rewrite my logic?
Problem
It's because you're not retaining your Disposables, so they're being deallocated immediately, and thus do nothing.
In getAll, you create an Observable<AType> via get_A_objects(), yet it is not added to a DisposeBag. When it goes out of scope (at the end of the func), it will be deallocated. So { aobj in msg.onNext ("Fetching A \(aobj)") } will never happen (or at least isn't likely to, if it's async).
Also, you aren't retaining the ReplaySubject<String> returned from getAll().subscribeNext either. So for the same reason, this would also be a deal-breaker.
Solution
Since you want two Observables: one for the actual final results (Observable<CType>), and one for the progress status (ReplaySubject<String>), you should return both from your getAll() function, so that both can be "owned", and their lifetime managed.
func getAll() -> (Observable<CType>, ReplaySubject<String>) {
let progress = ReplaySubject<String>.createUnbounded()
let results = self.get_A_objects()......
return (results, progress)
}
let (results, progress) = getAll()
progress
.subscribeNext {
print ($0)
}
.addDisposableTo(disposeBag)
results
.subscribeNext {
print ($0)
}
.addDisposableTo(disposeBag)
Some notes:
You shouldn't need to use createUnbounded, which could be dangerous if you aren't careful.
You probably don't really want to use ReplaySubject at all, since it would be a lie to say that you're "fetching" something later if someone subscribes after, and gets an old progress status message. Consider using PublishSubject.
If you follow the above recommendation, then you just need to make sure that you subscribe to progress before results to be sure that you don't miss any progress status messages, since the output won't be buffered anymore.
Also, just my opinion, but I would re-word "Fetching X Y" to something else, since you aren't "fetching", but you have already "fetched" it.

Combining two Observable<Void>s

I'm still a reactive newbie and I'm looking for help.
func doA() -> Observable<Void>
func doB() -> Observable<Void>
enum Result {
case Success
case BFailed
}
func doIt() -> Observable<Result> {
// start both doA and doB.
// If both complete then emit .Success and complete
// If doA completes, but doB errors emit .BFailed and complete
// If both error then error
}
The above is what I think I want... The initial functions doA() and doB() wrap network calls so they will both emit one signal and then Complete (or Error without emitting any Next events.) If doA() completes but doB() errors, I want doIt() to emit .BFailed and then complete.
It feels like I should be using zip or combineLatest but I'm not sure how to know which sequence failed if I do that. I'm also pretty sure that catchError is part of the solution, but I'm not sure exactly where to put it.
--
As I'm thinking about it, I'm okay with the calls happening sequentially. That might even be better...
IE:
Start doA()
if it completes start doB()
if it completes emit .Success
else emit .BFailed.
else forward the error.
Thanks for any help.
I believe .flatMapLatest() is what you're looking for, chaining your observable requests.
doFirst()
.flatMapLatest({ [weak self] (firstResult) -> Observable<Result> in
// Assuming this doesn't fail and returns result on main scheduler,
// otherwise `catchError` and `observeOn(MainScheduler.instance)` can be used to correct this
// ...
// do something with result #1
// ...
return self?.doSecond()
}).subscribeNext { [weak self] (secondResult) -> Void in
// ...
// do something with result #2
// ...
}.addDisposableTo(disposeBag)
And here is .flatMapLatest() doc in RxSwift.
Projects each element of an observable sequence into a new sequence of observable sequences and then
transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of map + switchLatest operator.
I apologize that I don't know the syntax for swift, so I'm writing the answer in c#. The code should be directly translatable.
var query =
doA
.Materialize()
.Zip(doB.Materialize(), (ma, mb) => new { ma, mb })
.Select(x =>
x.ma.Kind == NotificationKind.OnError
|| x.mb.Kind == NotificationKind.OnError
? Result.BFailed
: Result.Success);
Basically the .Materialize() operator turns the OnNext, OnError, and OnCompleted notifications for an observable of type T into OnNext notifications for an observable of type Notification<T>. You can then .Zip(...) these and check for your required conditions.
I've learned RxSwift well enough to answer this question now...
func doIt() -> Observable<Result> {
Observable.zip(
doA().map { Result.Success },
doB().map { Result.Success }
.catch { _ in Observable.just(Result.BFailed) }
) { $1 }
}

how to translate an if-else in RxSwift?

I'm trying to learn the library RxSwift
I have some code like this:
if data.checkAllIsOk()
{
[do things]
}
else
{
[show alert]
}
Now i need to update the data from the server before checking, so i have modeled a getData() that return an Observable.
My current approach is this:
getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0 // workaround for type inference bugs
return just(data.checkAllIsOk())
}
>- subscribeNext{ (ok) -> Void in
if ok
{
[do the things]
}
else
{
[show the alert]
}
}
>- disposeBag.addDisposable()
It works (or it should, i'm still writing it), but it feels wrong.. is there a more "reactive" way to do it?
What are the most appropriate operators to use?
Maybe returning an error for “false” and use the catch block?
Update
Following the approach suggested by ssrobbi i splitted the 2 branches in 2 different subscribeNext, and used filter to select the positive or negative branch. This is the code resulting:
let checkData=getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0
return just(data.checkAllIsOk())
}
>- shareReplay(1)
}
[...]
checkData
>- filter{ (ok) -> Bool in
ok == true
}
>- subscribeNext{ (_) -> Void in
[do the things]
}
>- disposeBag.addDisposable()
checkData
>- filter{ (ok) -> Bool in
ok == false
}
>- subscribeNext{ (_) -> Void in
[show the alert]
}
>- disposeBag.addDisposable()
The advantage of this approach is that i can reuse only one of the two branches in other parts of the code, without rewriting the subscribe body (less duplication is always good!)
Update
After some discussions in the RxSwift slack i added the shareReplay(1), so the getData() isn't repeated.
So to be honest I'm still learning as well, and I don't have RxSwift in front of me right now (so someone correct me if I'm spewing BS), but maybe I can give lead you into the right direction.
Your solution does work, but as you said it isn't very "reactive". I think the problem is that you have your data flow set up in a way that it has to make an imperative decision on whether to show an alert or do stuff. What should happen, is that instead of getData returning an observable, getData should get whatever data it needs to (whether it's from a network, core data, etc), and then it should update an observable property.
For the do things:
Now, you would observe that property, map it to check if it's okay, and subscribe to it like you did, check if it's true, and if it is do things. (and add disposable)
For the alert:
You'd do the exact same thing, observing that same property again, but check for the opposite case and do stuff.
I think what's not super reactive about it is that you're synchronously waiting on a response from that getData() function, which creates a scenario where you now have state, whether to show an alert, or do that extra work. They're not derived from the value stream of some other property. Showing the alert and doing things are only related to each other because you set up your code imperatively.
EDIT: Instead of checking with an if statement whether or not that's true, perhaps you could put it through a filter before subscribing instead.
I don't know RXSwift, but I do know functional programming (RXSwift is functional as well). An if else statement is already on its lowest form and you don't need to do functional branching, which would work but it makes it less readable.
You could change your if else to condition ? a : b if you want to be more functional (Haskell's if else is exactly that). But that makes it less readable as well, I would just stick to what you got ;)
getData() should return an Observable<Data> and the contained data should already be ok. In other words, if getData() is implemented correctly, then calling data.checkAllIsOk() on the data pushed out of the observable should always return true.
So what you should have outside of getData() is something like (in Rx v2 with Swift v2):
getData().subscribe { event in
switch event {
case .Next(let data):
[do things with data]
case .Error(let error):
[show the alert]
}
The problem with your updated approach is that checkData is an Observable<Bool>, so you can't really "do stuff" with it.
I think what you want is this (decomposed for clarity):
func isDataOk(_ data: Data) -> Bool { /* ... */ }
let data = getData().shareReplay(1)
let goodData = data.filter(isDataOk)
let badData = data.filter{ isDataOk($0) == false }
goodData
.subscribe( /* do stuff */ )
.addDisposableTo(disposeBag)
badData
.subscribe( /* show alert */ )
.addDisposableTo(disposeBag)
But I agree with Daniel in that this is a great opportunity to use an observable that errors. Something like this:
func passOrThrow(_ data: Data) throws -> Data { /* throw an error here if data is bad */ }
getData()
.map(passOrThrow)
.subscribe(onNext: { data in
// do the things
}, onError: { error in
// show the alert
}).addDisposableTo(disposeBag)