Need help to resolve issue with return value in RxJava. So, here is what I have done so far:
private Observable<Boolean> updatedMenuItems() {
return Observable.fromIterable(getAliases())
.doOnNext(alias -> {
return ApiRepository.getMenuItemBlocks(alias)
.subscribeOn(Schedulers.io())
.flatMapIterable(list -> list)
.map(item -> item.getFileTimeStamp() == null ? "" : item.getFileTimeStamp().toString())
.toList()
.doOnSuccess(apiContents -> {
return Observable.fromCallable(DataStoreRepository::loadMenuItemsBlock)
.subscribeOn(Schedulers.io())
.flatMapIterable(list -> list)
.map(item -> item.fileTimeStamp == null ? "" : item.fileTimeStamp.toString())
.toList()
.map(dbContents -> {
return DBUtil.isContentEqual(dbContents, apiContents);
});
});
});
}
I need to return Observable<Boolean> data type from this method but inside the block of .doOnSuccess(...); I am getting an error concerning return value. Could you please, help me to solve this issue. Thanks!
doOnNext, doOnSuccess, doOnError, doOnCompleted
These are called Side-effect operators, they don't really affect the chain in anyway, they might be used as a logging mechanism for ex.
What you're looking for is probably flatMap, from the docs:
FlatMap: Transforms the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.
Related
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.
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
}
I've been working on a search controller that is using RxSwift to update DataSource when user types in Search field, like it's described here: http://www.thedroidsonroids.com/blog/ios/rxswift-examples-3-networking/
That is my viewmodel:
struct SearchControllerViewModel {
let provider: RxMoyaProvider<ThreadifyEndpoint>
let startsWith: Observable<String>
func startSearching() -> Observable<[SearchedNodeViewModel]> {
return startsWith
.observeOn(MainScheduler.instance)
.flatMapLatest { query -> Observable<[SearchedNodeViewModel]?> in
return self.findNodes(query)
}.replaceNilWith([])
}
internal func findNodes(startsWith: String) -> Observable<[SearchedNodeViewModel]?> {
return self.provider
.request(ThreadifyEndpoint.SearchForNodes(startsWith: startsWith))
.mapArrayOptional(SearchedNodeViewModel.self)
}
}
Now I want new data to be loaded not only when user is typing but either when sh's scrolling down.
I was thinking to use combineLatest to observe both rx_text and rx_offset but I can't pass Observable to combineLatest because of compilation error.
The compilation error you're seeing is due to you not using an actual method. The method signature you're intending to use is:
public class func combineLatest<O1 : ObservableType, O2 : ObservableType>(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> Element) -> RxSwift.Observable<Element>
Notice that there's a third argument that you're forgetting: resultSelector. That's supposed to be a block that describes how you want to combine the latest elements into a new element.
Based on your error message, I'm thinking you're using it like this:
let combined = Observable.combineLatest(stringObservable, pointObservable)
Whereas, you should be using it like this:
let combined = Observable.combineLatest(stringObservable, pointObservable) { (s, p) in
return "\(s), \(p)" // or construct your new element however you'd like
}
Without the block, RxSwift doesn't know how you'd like to combine them. You might have been thinking it would just default to making a new tuple of (String, CGPoint) as the element, but it makes no such assumptions and requires you to tell it.
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 }
}
Here is the code...
login().then {
// our login method wrapped an async task in a promise
return API.fetchKittens()
}.then { fetchedKittens in
// our API class wraps our API and returns promises
// fetchKittens returned a promise that resolves with an array of kittens
self.kittens = fetchedKittens
self.tableView.reloadData()
}.catch { error in
// any errors in any of the above promises land here
UIAlertView(…).show()
}
See how the then method is not returning anything.
When I use then the compiler is saying I must return a promise. Why don't I have the choice not to?
The error goes away when I add a catch clause straight after. huh?
Answer here.
I added -> Void after my closure parameter.
.then { fetchedKittens -> Void in }
You should use .done{} to finish promise chain, like this:
.done { fetchedKittens -> Void in }
.then{} with -> Void is not working anymore, because .then{} always need to return promise.
Documentation