Search code examples
swiftrxjsreactive-programmingrx-swiftreactivex

RxSwift equivalent for switchmap


In RxJS you can use the value from a observable for a new observable. For example:

this.authService.login(username, password).pipe(
  switchMap((success: boolean) => {
    if(success) {
      return this.contactService.getLoggedInContact()
    } else {
      return of(null)
    }
  })
).subscribe(contact => {
  this.contact = contact
})

But now I have to do a project in Swift and I want to achieve the same thing. I can get the two methods working, but using the result of the first observable for the second observable is something i can't get working. The switchMap pipe is something that does not exist in RxSwift and I cannot find the equivalent.

I've tried mapping the result of the login function to the observable and then flatmapping it, but unfortunately that didn't work.

What is the best way to do this in Swift without using a subscribe in a subscribe?

EDIT I've tried flat map:

APIService.login(email: "username", password: "password")
  .flatMapLatest { result -> Observable<Contact> in
    if result {
      return APIService.getLoggedInContact()
    } else {
      return .of()
    }
  }.subscribe(onNext: {result in
    print("Logged in contact: \(result)")
  }, onError: {Error in
    print(Error)
  }).disposed(by: disposeBag)

But unfortunately that didn't work, I get an error Thread 1: EXC_BAD_ACCESS (code=1, address=0x13eff328c)

EDIT2:

This is the login function

static func login(email: String, password: String) -> Observable<Bool> {
        return Observable<String>.create { (observer) -> Disposable in
            Alamofire.request(self.APIBASEURL + "/contact/login", method: .post, parameters: [
                "email": email,
                "password": password
            ], encoding: JSONEncoding.default).validate().responseJSON(completionHandler: {response in
                if (response.result.isSuccess) {
                    guard let jsonData = response.data else {

                        return observer.onError(CustomError.api)
                    }
                    let decoder = JSONDecoder()

                    let apiResult = try? decoder.decode(ApiLogin.self, from: jsonData)
                    return observer.onNext(apiResult!.jwt)
                } else {
                    return self.returnError(response: response, observer: observer)
                }
            })

            return Disposables.create()
        }.map{token in
            return KeychainWrapper.standard.set(token, forKey: "authToken")
        }
    }

This is the getLoggedInContact function

static func getLoggedInContact() -> Observable<Contact> {
        return Observable.create { observer -> Disposable in
            Alamofire.request(self.APIBASEURL + "/contact/me", method: .get, headers: self.getAuthHeader())
              .validate().responseJSON(completionHandler: {response in
                if (response.result.isSuccess) {
                    guard let jsonData = response.data else {

                        return observer.onError(CustomError.api)
                    }

                    let decoder = JSONDecoder()
                    decoder.dateDecodingStrategy = .formatted(.apiNewsDateResult)

                    let apiResult = try? decoder.decode(Contact.self, from: jsonData)
                    return observer.onNext(apiResult!)
                } else {
                    return self.returnError(response: response, observer: observer)
                }
            })

            return Disposables.create()
        }
    }

Solution

  • There is operator flatMapLatest which does exactly the same as switchMap in RxJS.

    You can find usage example here