1

So, i have this login function, where i want to return a Boolean to my .xib controller, where it has to make some modifications, according to the success of the login.

 func login(cpf: String) -> Bool {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    var success = false

    dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .subscribe(onNext: { (userToken) in
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)
            userData.synchronize()
            success = true
    }, onError: { (error) in
        print("Login Error: \(error)")
    }).disposed(by: disposeBag)

    return success
}

The return from postSimpleResponse() method is an Observable of type Any?.

The thing is, that my login() method is returning before the success boolean variable is set true in the subscribe.

How could i resolve this ?

Mukesh
  • 2,792
  • 15
  • 32
Emmanuel
  • 109
  • 1
  • 9
  • The whole point of RX is that you model asynchronous tasks with Observables. If you exit the observable "world" then you're defeating much of the purpose of using it. The correct thing to do here would be to emit a `var loginState: Observable`, and subscribe to it to update your UI whenever there's a change. – Alexander Jan 15 '19 at 22:24
  • Note: This is NOT a duplicate of that question. If you're using RX, completion handlers are *not* the correct solution to asynchronous data modelling. – Alexander Jan 15 '19 at 22:24

2 Answers2

1

You can map the Observable to a Bool observable (Although Bool is not necessary here, you can just use Void), and return Observable from login method.

Like this:

func login(cpf: String) -> Observable<Bool> {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    return dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .do(onNext: {
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)
        })
        .map { _ in
            return true
    }
}

And then observe on it:

login(cpf: "data").subscribe(onNext: { _ in
    // Success
}, onError: { _ in
    // Failure
}).disposed(by: disposeBag)
Mukesh
  • 2,792
  • 15
  • 32
0

As it was said before:

The whole point of Rx is that you model asynchronous tasks with Observables.

But if you really need to make observable synchronous (wait till the onNext, or perhaps onCompleted) you could use DispatchGroup (monitor).

Your code should look like:

func login(cpf: String) -> Bool {
    let url = URL(string: AccessibilityDataAccessProvider.kLoginURL)
    let params = ["username": String.init(describing: cpf)]

    var result = false

    let group = DispatchGroup()
    group.enter()

    let subscription = dataAccessProvider.postSimpleResponse(url: url!, params: params)
        .subscribe(onNext: {
            let userData = UserDefaults.standard
            userData.set(userToken, forKey: AccessibilityDataAccessProvider.userTokenKey)

            result = true
            group.leave()
        }, onError: { _ in 
            group.leave()
        })

    group.wait() // wait till leave() method called
    subscription.dispose()

    return result
}

You should remember that this approach blocks current thread, till execution of async code is finished. Therefore, it's not perfect, but sometimes it could be necessary.

Konstantin Nikolskii
  • 1,075
  • 1
  • 12
  • 17