API call executed only one time using RxSwift - swift

I have this code with seems to be correct. But it's only react on first search change. So the code is executed only one time. I tried to add concat(Observable.never()) to my getAl function but it still running only one time. Did I miss something ?
exists = search.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { searchString -> Observable<Bool> in
guard !searchString.isEmpty else {
return Observable.empty()
}
return ServiceProvider.food.getAll(whereFoodName: searchString)
.flatMap({ (result) -> Observable<Bool> in
return Observable.just(result.count > 0)
})
}

Your code just return an Observable. To work with it you should observe it (or rather subscribe to it in Rx terminology)
You'll probably want something like this:
search.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.subscribe(onNext: { searchString in
let exists = ServiceProvider.food.getAll(whereFoodName: searchString).count > 0
print("Exists: \(exists)")
// Or do whatever you want with `exists` constant
// You could call a method to update UI
if exists {
self.button.enabled = true
}
})
.disposed(by: disposeBag) //disposeBag should be your property which will be deallocated on deinit

Related

ReactiveSwift pipeline flatMap body transform not executed

I have the following pipeline setup, and for some reason I can't understand, the second flatMap is skipped:
func letsDoThis() -> SignalProducer<(), MyError> {
let logError: (MyError) -> Void = { error in
print("Error: \(error); \((error as NSError).userInfo)")
}
return upload(uploads) // returns: SignalProducer<Signal<(), MyError>.Event, Never>
.collect() // SignalProducer<[Signal<(), MyError>.Event], Never>
.flatMap(.merge, { [uploadContext] values -> SignalProducer<[Signal<(), MyError>.Event], MyError> in
return context.saveSignal() // SignalProducer<(), NSError>
.map { values } // SignalProducer<[Signal<(), MyError>.Event], NSError>
.mapError { MyError.saveFailed(error: $0) } // SignalProducer<[Signal<(), MyError>.Event], MyError>
})
.flatMap(.merge, { values -> SignalProducer<(), MyError> in
if let error = values.first(where: { $0.error != nil })?.error {
return SignalProducer(error: error)
} else {
return SignalProducer(value: ())
}
})
.on(failed: logError)
}
See the transformations/signatures starting with the upload method.
When I say skipped I mean even if I add breakpoints or log statements, they are not executed.
Any idea how to debug this or how to fix?
Thanks.
EDIT: it is most likely has something to do with the map withing the first flatMap, but not sure how to fix it yet.
See this link.
EDIT 2: versions
- ReactiveCocoa (10.1.0):
- ReactiveObjC (3.1.1)
- ReactiveObjCBridge (6.0.0):
- ReactiveSwift (6.1.0)
EDIT 3: I found the problem which was due to my method saveSignal sending sendCompleted.
extension NSManagedObjectContext {
func saveSignal() -> SignalProducer<(), NSError> {
return SignalProducer { observer, disposable in
self.perform {
do {
try self.save()
observer.sendCompleted()
}
catch {
observer.send(error: error as NSError)
}
}
}
}
Sending completed make sense, so I can't change that. Any way to change the flatMap to still do what I intended to do?
I think the reason your second flatMap is never executed is that saveSignal never sends a value; it just finishes with a completed event or an error event. That means map will never be called, and no values will ever be passed to your second flatMap. You can fix it by doing something like this:
context.saveSignal()
.mapError { MyError.saveFailed(error: $0) }
.then(SignalProducer(value: values))
Instead of using map (which does nothing because there are no values to map), you just create a new producer that sends the values after saveSignal completes successfully.

Branching Combine operator, flatMap, and debounce oddity

Context
I am following the example from WWDC 2019 722 https://developer.apple.com/videos/play/wwdc2019/722/ and WWDC 2019 721 https://developer.apple.com/videos/play/wwdc2019/721/ and making a field with validation the runs an asynchronous network check on a field.
What should happen, as mentioned in the talk, is that the username field should:
Debounce
Show a loading indicator
Perform the network request
End with the network result
Hide the loading indicator
And show or hide a validation message as a result of the network response
I have a prototype that has the debounce, and mocks the network request by using the delay operator. All of this is working well for the most part.
let a = $firstName
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.flatMap { name -> AnyPublisher<String, Never> in
if name == "" {
return Just(name)
.eraseToAnyPublisher()
} else {
return Just(name)
.handleEvents(receiveOutput: { _ in self.isFirstNameLoading = true})
.delay(for: .seconds(2), scheduler: DispatchQueue.main)
.handleEvents(receiveOutput: { _ in self.isFirstNameLoading = false})
.eraseToAnyPublisher()
}
}
.map { name -> Bool in name != "Q" }
.assign(to: \.isFirstNameValid, on: self)
The debounce waits until the input has paused. The flatMap acts as a conditional branching in the Combine flow of operators: if the value is empty, do not bother with the network request; else, if the value has value after the debounce, perform the network request. Lastly, my example is that "Q" is always an error, for mock purposes.
However, the slight problem is that the debounce happens before the branching. I would like to move the debounce to the else branch of the conditional, like so.
let a = $firstName
.flatMap { name -> AnyPublisher<String, Never> in
if name == "" {
return Just(name)
.eraseToAnyPublisher()
} else {
return Just(name)
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.handleEvents(receiveOutput: { _ in self.isFirstNameLoading = true})
.delay(for: .seconds(2), scheduler: DispatchQueue.main)
.handleEvents(receiveOutput: { _ in self.isFirstNameLoading = false})
.eraseToAnyPublisher()
}
}
.map { name -> Bool in name != "Q" }
.assign(to: \.isFirstNameValid, on: self)
When this happens, the true branch of the conditional (empty input) is run correctly, and the map+assign after the flatMap run correctly. However, when the input has value, and the else branch of the conditional runs, nothing after the debounce is run at all.
I have tried switching the DispatchQueue to OperationQueue.main and RunLoop.main to no avail.
Keeping the debounce to before the conditional works okay for now, but I'm wondering if I'm doing anything wrong with my attempt to put it in the branch. I'm also wondering if this would be the correct way to do "branching" in operators with Combine, particularly with my use of flatMap and Just().
Any help would be appreciated!
A Just only ever produces one output. Attaching a debounce to it is not going to debounce anything. At best, it will just delay the output of the Just by the debounce interval. At worst, there's a bug preventing it from working at all, which is what it sounds like based on your description.

RxSwift Skip Events Until Own Sequence has finished

I have one observable (we will call it trigger) that can emit many times in a short period of time. When it emits I am doing a network Request and the I am storing the result with the scan Operator.
My problem is that I would like to wait until the request is finished to do it again. (But as it is now if trigger emits 2 observables it doesn't matter if fetchData has finished or not, it will do it again)
Bonus: I also would like to take only the first each X seconds (Debounce is not the solution because it can be emitting all the time and I want to get 1 each X seconds, it isn't throttle neither because if an observable emits 2 times really fast I will get the first and the second delayed X seconds)
The code:
trigger.flatMap { [unowned self] _ in
self.fetchData()
}.scan([], accumulator: { lastValue, newValue in
return lastValue + newValue
})
and fetchData:
func fetchData() -> Observable<[ReusableCellVMContainer]>
trigger:
let trigger = Observable.of(input.viewIsLoaded, handle(input.isNearBottomEdge)).merge()
I'm sorry, I misunderstood what you were trying to accomplish in my answer below.
The operator that will achieve what you want is flatMapFirst. This will ignore events from the trigger until the fetchData() is complete.
trigger
.flatMapFirst { [unowned self] _ in
self.fetchData()
}
.scan([], accumulator: { lastValue, newValue in
return lastValue + newValue
})
I'm leaving my previous answer below in case it helps (if anything, it has the "bonus" answer.)
The problem you are having is called "back pressure" which is when the observable is producing values faster than the observer can handle.
In this particular case, I recommend that you don't restrict the data fetch requests and instead map each request to a key and then emit the array in order:
trigger
.enumerated()
.flatMap { [unowned self] count, _ in
Observable.combineLatest(Observable.just(count), self.fetchData())
}
.scan(into: [Int: Value](), accumulator: { lastValue, newValue in
lastValue[newValue.0] = newValue.1
})
.map { $0.sorted(by: { $0.key < $1.key }).map { $0.value }}
To make the above work, you need this:
extension ObservableType {
func enumerated() -> Observable<(Int, E)> {
let shared = share()
let counter = shared.scan(0, accumulator: { prev, _ in return prev + 1 })
return Observable.zip(counter, shared)
}
}
This way, your network requests are starting ASAP but you aren't loosing the order that they are made in.
For your "bonus", the buffer operator will do exactly what you want. Something like:
trigger.buffer(timeSpan: seconds, count: Int.max, scheduler: MainScheduler.instance)
.map { $0.first }

Returning Observable is not working

I am just playing with RxSwift to better understand the concepts. I am trying to validate email. However, the subscriber doesn't call when I return an Observable back. I thought this might work. Any suggestions?
var emailAddressValid: Observable<String>{
return self.userEmailAddress.asObservable().filter({ (userEmail) -> Bool in
userEmail.count > 0
}).flatMap({ (userEmail) -> Observable<String> in
if userEmail == "abcd#gmail.com"{
//This works perfectly
return .just("007")
} else {
let emailDomain = userEmail.components(separatedBy: "#").last
if emailDomain != nil {
//nothing happens in onNext
//This returns back an Observable<String>
return DataService.instance.checkDomainIsRegistered(domainName: emailDomain!)
} else {
return .just("0")
}
}
})
}
Although the app works. However, there isn't any compiler error as well. But the onNext in the Observer doesn't work when I return DataService.instance.checkDomainIsRegistered(domainName: emailDomain!)
func checkDomainIsRegistered(domainName: String) -> Observable<String>{
print(domainName)
return Observable<String>.create{ data in
self._REF_DOMAIN_NAMES.queryOrdered(byChild: "domainName").queryEqual(toValue: domainName).observeSingleEvent(of: .value, with: { (domainNameSnapshot) in
if(domainNameSnapshot.exists()){
print("1")
data.onNext("1")
} else {
print("0")
data.onNext("0")
}
}, withCancel: { (error) in
data.onError(error)
})
data.onCompleted()
return Disposables.create()
}
}
In your example, the call to
self._REF_DOMAIN_NAMES.queryOrdered(byChild: "domainName")
.queryEqual(toValue: domainName)
.observeSingleEvent(of: .value, with: { (domainNameSnapshot) in })
is likely being dispatched to a background thread or queue.
In the meantime, your Observable checkDomainIsRegistered then completes with data.onCompleted() while probably running on the main thread.
The results is that onNext() is never being allowed to be called. You can verify this is happening by temporarily removing the onCompleted().
And if called data.onError(error), event stream may be broken. You should catch that.

RxSwift Wait For Observable to Complete

I'm new to rxswift and here's my problem:
Suppose I have observable of actions: Observable.of("do1", "do2", "do3")
Now this observable mapped to function that returns observable:
let actions = Observable.of("do1", "do2", "do3")
func do(action: String) -> Observable<Result> {
// do something
// returns observable<Result>
}
let something = actions.map { action in return do(action) } ???
How can I wait for do1 to complete first, then execute do2, then do3?
Edit: Basically i want to achieve sequential execution of actions. do3 waits for do2 result, do2 waits for do1 result.
Edit2: I've tried using flatmap and subscribe, but all actions runs in parallel.
How can I wait for do1 to complete first, then execute do2, then do3?
I think concatMap solves the problem.
Lets say we have some service and some actions we need to perform on it, for instance a backend against with we'd like to authenticate and store some data. Actions are login and store. We can't store any data if we aren't logged in, so we need to wait login to be completed before processing any store action.
While flatMap, flatMapLatest and flatMapFirst execute observables in parallel, concatMap waits for your observables to complete before moving on.
import Foundation
import RxSwift
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let service: [String:Observable<String>] = [
"login": Observable.create({
observer in
observer.onNext("login begins")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
observer.onNext("login completed")
observer.onCompleted()
})
return Disposables.create()
}),
"store": Observable.create({
observer in
observer.onNext("store begins")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
observer.onNext("store completed")
observer.onCompleted()
})
return Disposables.create()
}),
]
// flatMap example
let observeWithFlatMap = Observable.of("login", "store")
.flatMap {
action in
service[action] ?? .empty()
}
// flatMapFirst example
let observeWithFlatMapFirst = Observable.of("login", "store")
.flatMapFirst {
action in
service[action] ?? .empty()
}
// flatMapLatest example
let observeWithFlatMapLatest = Observable.of("login", "store")
.flatMapLatest {
action in
service[action] ?? .empty()
}
// concatMap example
let observeWithConcatMap = Observable.of("login", "store")
.concatMap {
action in
service[action] ?? .empty()
}
// change assignment to try different solutions
//
// flatMap: login begins / store begins / store completed / login completed
// flatMapFirst: login begins / login completed
// flatMapLatest: login begins / store begins / store completed
// concatMap: login begins / login completed / store begins / store completed
let observable = observeWithConcatMap
observable.subscribe(onNext: {
print($0)
})
I just face the same problem, and finally found the solution.
I expect my devices will do disconnect followed by one another, so I did as follow:
I just create the func like
func disconnect(position: WearingPosition) -> Completable{
print("test run")
return Completable.create { observer in
print("test run 2")
// Async process{
// observer(.complete)
// }
return Disposables.create()
}
}
And use like:
self.disconnect(position: .left_wrist).andThen(Completable.deferred({
return self.disconnect(position: .right_wrist)
})).subscribe(onCompleted: {
// do some things
}) { (error) in
print(error)
}.disposed(by: self.disposeBag)
The key is the usage of " Completable.deferred "
I have tested with the "test run" printed
Use flatMap or flatMapLatest. You can find more about them at reactivex.io.
You can flatMap and subscribe to the Result observable sequence by calling subscribe(on:) on the final output.
actions
.flatMap { (action) -> Observable<Result> in
return self.doAction(for: action)
}
.subscribe(onNext: { (result) in
print(result)
})
func doAction(for action: String) -> Observable<Result> {
//...
}
Read:
https://medium.com/ios-os-x-development/learn-and-master-%EF%B8%8F-the-basics-of-rxswift-in-10-minutes-818ea6e0a05b