Search code examples
swiftnetwork-programmingobservablerx-swiftchaining

RxSwift Chaining requests


The problem I faced with is chaining 2 requests and handling errors. Below an example of my code:

func fbLogin() -> Observable<String> { ... }

func userLogin(request: Request) -> Observable<User> { ... }

let signedWithLogin = loginTaps
    .asDriver(onErrorJustReturn: ())
    .flatMapLatest { _ in
        return fbLogin()
        .map({ ReqestState<String>.loaded($0) })
        .asDriver(onErrorRecover: { error  in
            return Driver.just(.error(error))
        })
        .startWith(.loading)
     }
     .map({ UserEndpoint.socialLogin(token: $0) })
     .flatMapLatest { request in
         return userLogin(request: request)
         .map({ ReqestState<User>.loaded($0) })
         .asDriver(onErrorRecover: { error  in
              return Driver.just(.error(error))
         })
         .startWith(.loading)
     }

signedWithLogin
    .drive(onNext: { response in
        print(response)
     })
    .disposed(by: disposeBag)

Problem is when I cancel the facebook login popup I send observer.onError(FBLoginManagerError.canceled) error. This error catch first .asDriver(onErrorRecover: { error method but does't pass to the .drive(onNext: { responsemethod.

How can I catch all errors in .asDriver(onErrorRecover: { error method?


Solution

  • Mukesh is correct that you probably should avoid Driver until the end. Also, there's little point of having both RequestState types when you only really care about the final one (RequestState<User>)

    Here's a simpler version that I think will do what you want:

    let signedWithLogin = loginTaps
        .flatMapLatest {
            fbLogin()
                .map { UserEndpoint.socialLogin(token: $0) }
                .flatMap { userLogin(request: $0) }
                .map { RequestState.loaded($0) }
                .catchError { .just(.error($0)) }
                .startWith(.loading)
        }
        .asDriver(onErrorRecover: { fatalError($0.localizedDescription) }) // I'm using `fatalError()` here because if the above emits an error something has gone horribly wrong (like the RxSwift library isn't working anymore.)
    
    signedWithLogin
        .drive(onNext: { response in
            print(response)
        })
        .disposed(by: disposeBag)
    

    The above assumes you change your UserEndpoint.socialLogin(token:) function to accept a String instead of a RequestState<String>.

    It also assumes that fbLogin() and userLogin(request:) only emit one value each. You might want to consider switching them to Singles.