1

I'm trying to combine facebook login with a rest call, so when the user is logged in it should make an authenticate call to the server, where the server makes the graph calls, however I'm a bit confused to how I nest the calls with RxSwift? so far I have a FacebookProvider class with following method

func login() -> Observable<String> {
    return Observable.create({ observer in

        let loginManager = LoginManager()

        //LogOut before
        loginManager.logOut()

        //Set Login Method
        loginManager.loginBehavior = .native

        //Login Closure
        loginManager.logIn([ .publicProfile, .userFriends, .email], viewController: self.parentController) { loginResult in
            switch loginResult {
            case .failed(let error):
                print(error)
                observer.onError(FacebookError.NoConnection(L10n.networkError))
            case .cancelled:
                print("User cancelled login.")
            case .success(_, let declinedPermissions, let accessToken):
                print("Logged in!")

                guard declinedPermissions.count > 0 else {
                    observer.onError(FacebookError.DeclinedPermission(L10n.declinedPermission))
                    return
                }

                observer.onNext(accessToken.authenticationToken)
                observer.onCompleted()

            }
        }


        return Disposables.create()
    })

}

Then I have a LoginViewModel with this model

public func retrieveUserData() -> Observable<User> {
    return Network.provider
                .request(.auth(fbToken: Globals.facebookToken)).retry(5).debug().mapObject(User.self)
}

then I in my UIViewController do this

    facebookProvider.validate().subscribe({ [weak self] response in

        switch response {
        case .error(_):
            // User is not logged in push to loginController
            break

        case .next():
            //user is logged in retrieveUserData before proceeding

            self?.loginViewModel.retrieveUserData().subscribe { event in
                switch event {
                case .next(let response):
                    print(response)
                case .error(let error):
                    print(error)
                case .completed:
                    print("completed")
                }
            }.addDisposableTo(self?.disposeBag)


            break
        case .completed:
            //data is retrieved and can now push to app
            break


        }

    }).addDisposableTo(disposeBag)

Validate

public func rx_validate() -> Observable<String> {
    return Observable.create({ observer in
        //Check if AccessToken exist
        if AccessToken.current == nil {
            observer.onError(FacebookError.NotLoggedIn)
        } else {
            observer.onNext(Globals.accessToken)
        }
        observer.onCompleted()
        return Disposables.create()
    })
}
Peter Pik
  • 11,023
  • 19
  • 84
  • 142

1 Answers1

4

You will want to use flatMap

The closure passed to flatMap will return an observable. flatMap will then take care of un-nesting it, meaning if the closure returns a value of type Observable<T>, and you call flatMap on a value of type Observable<U>, the resulting observable will be Observable<T> (an not Observable<Observable<T>>

In this particular case, the code would look like this:

facebookProvider.validate().flatMap { [weak self] _ in
  return self?.loginViewModel.retrieveUserData()
}.subscribe { event in
  switch event {
    // ...
  }
}.addDisposableTo(disposeBag)

On a side note, you should probably update func retrieveUserData() to accept the token as a parameter, instead of fetching it from your Globals structure.

The resulting code would look similar to this

public func retrieveUserData(token: String) -> Observable<User> {
    return Network.provider
            .request(.auth(fbToken:  token)).retry(5).debug().mapObject(User.self)
}

in viewController

facebookProvider.validate().flatMap { [weak self] token in
  return self?.loginViewModel.retrieveUserData(token: token)
}.subscribe { event in
  switch event {
    // ...
  }
}.addDisposableTo(disposeBag)
tomahh
  • 13,441
  • 3
  • 49
  • 70
  • why is `addDisposableTo(disposeBag)` not used here? – Peter Pik Jan 11 '17 at 10:59
  • Simply an omission on my part, sorry about the confusion. I've updated the answer. – tomahh Jan 11 '17 at 11:00
  • I've added the `validate` function with minor name change, this is just a simple function checking if the `AccessToken` already is saved and thereby pass it in next, however this gives me following error `Cannot convert value of type (_) -> Observable to expected argument type (string) -> O` – Peter Pik Jan 11 '17 at 11:06
  • I've tested it and it seem to be an issue with `[weak self]` since removing it works – Peter Pik Jan 11 '17 at 11:15
  • And how do I get subscribe event for `validate()` in the viewController since the bottom code does not seem to print anything in either `error`, `next` and `completed`. guess this is due to error event in `validate()` – Peter Pik Jan 11 '17 at 11:23
  • Be careful about removing `[weak self]` or `[unowned self]` because it may create memory leaks, as explained by @tomahh here: http://stackoverflow.com/questions/40583685/using-self-on-rxswift-closures-what-about-instance-methods-as-param – dsapalo Jan 19 '17 at 18:55