Returning Observable is not working - swift

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.

Related

RxSwift, combining multiple observables, but only retrying one

I have a logic problem. The premise of what I would like to achieve is to combine two sources of data and in case of failure, I would like to run retry only on one observable. The logic flow goes like this: I try and fail to get local data from StorageManager class, afterwards I try to get the data from an API. In case that API call fails, I would like to retry only that API call a certain number of times. Is there a nice way of doing this? By just running retry like in the code below, the local data observable gets triggered.
class func loginUser(user: User, replaySubject: ReplaySubject<User>){
let savedUserObs = StorageManager.readCachedModelData(requestType: .LOGIN, modelType: User.self)
let apiUserObs = NetworkHelper.makeRequest(requestType: .LOGIN).map { response -> User in
guard let user = response.user?.first else { throw TempErrors.wasNotAbleToExtractUser }
return user
}
savedUserObs.concat(apiUserObs)
.retry { errorObs in
errorObs.scan(0) { attempt, error in
let max = 5
if attempt == max { throw TempErrors.tooManyRetries }
return attempt + 1
}
}
.subscribe { user in
replaySubject.onNext(user)
StorageManager.saveData(requestType: .LOGIN, data: user)
} onError: { error in
print("onerror error: \(error)")
} onCompleted: {
print("completed")
} onDisposed: {
print("disposed")
}.disposed(by: disposeBag)
}

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.

RxSwift catch networking and reachability errors

I try to use retryOnBecomesReachable method from the RX example files in my networking layer
extension ObservableConvertibleType {
func retryOnBecomesReachable(_ valueOnFailure:E, reachabilityService: ReachabilityService?) -> Observable<E> {
return self.asObservable()
.catchError { (e) -> Observable<E> in
return reachabilityService.reachability
.skip(1)
.filter { $0.reachable }
.flatMap({ _ -> Observable<E> in
return Observable.error(e)
})
.startWith(valueOnFailure)
}
.retry()
}
}
// My layer
request
.flatMapLatest{ request in
provider.rx.request(request)
.map{ User.self }
.map{ RequestState.loaded($0) }
.retryOnBecomesReachable(.error(.notConnectedToInternet), reachabilityService: reachabilityService)
.catchError({ .just(.error($0)) })
.startWith(.startLoading)
}
Without this method, all works awesome. All error catching and returning .just(.error($0)) sequence.
With this method, the retry feature works awesome. But when something happens (mapping, decoding or other error) I get .notConnectedToInternet. I think the reason in .startWith(valueOnFailure) method. I tried to move, remove, change position but nothing helps. I'm stuck.
What should I do to use retry feature and catch errors correct?
I think that basically changing .startWith(valueOnFailure) to startWith(e) might work for you. Another option is to check if the error is a reachability error to begin with inside the catch block.
e.g.
.catchError { e in
guard e == SomeError.notConnectedToInternet else {
return .error(e)
}
... rest of your code

Chaining operations with RxSwift

I want to chain following operations
createUserandVerify
Create Anonymous User (user)
Verify User -> verifiedUser
If verification successful return verifiedUser else return user
Get stuff with coredata getStuff
If stuff.count > 0 Upload stuff with user credentials uploadStuff
Finally report the result of all operations
I wrote createUserandVerify as below. I wonder how should I write uploadStuff in reactive way. Upload function depends on user credentials. Therefore It must only run after createUserandVerify. I know I could just check count of array inside uploadStuff and return empty but I wonder the best practices.
func createUserandVerify() -> Single<User> {
return Service.sharedInstance.generateAnonUser()
.flatMap{ user in
if Service.sharedInstance.isOldRegisteredUser {
print("It is old user")
// We need to verify the receipt
return Service.sharedInstance.verifyReceipt()
.flatMap { verifiedUser in
print("Returning Verified new user [Verification Success]")
return Single.just((verifiedUser))
}.catchError{ error ->Single<User> in
print("Returning firstly created user [Verification Failed]")
print("Error Type: \(error)")
return Single.just(user)
}
} else {
//Normal anonymous old user
print("Returning firstly created user [Anonymous]")
return Single.just(user)
}
}
}
Assumptions (since I have not worked with Single I changed them to Observable):
func createUserandVerify() -> Observable<User>
func getStuff() -> [Stuff]
func uploadStuff(_ user: User) -> Observable<String>
createUserandVerify() should publish errors with onError so uploadStuff will not be called if something goes wrong.
Possible solution:
enum CustomError: Error {
case instanceMissing
case notEnoughStuff
}
createUserandVerify()
.flatMap { [weak self] (user) -> Observable<String> in
guard let strongSelf = self else { throw CustomError.instanceMissing }
guard strongSelf.getStuff().count > 0 else { throw CustomError.notEnoughStuff }
return strongSelf.uploadStuff(user)
}
.subscribe(
onNext: { stringResult in
// print result from 'uploadStuff'
print(stringResult)
},
onError: { error in
// will be reached if something goes
// wrong in 'createUserandVerify' or 'uploadStuff'
// or if one of your custom errors in 'flatMap' are thrown
print(error)
})
.disposed(by: disposeBag)
You could also make getStuff reactive by returning an Observable or Single and also include it in the chain via flatMap.

API call executed only one time using RxSwift

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