jerrygdm
jerrygdm

Reputation: 450

call method only on rx_tap event

I have a basic problem with rxswift. I have a login screen that should call my login method inside view model when and only when I tap the login button

        loginButton.rx_tap
        .doOn({[unowned self] _ in
            self.loginButton.enabled = false
        })
        .flatMap({[unowned self] in self.loginModel.login() })
        .subscribeNext({ [weak self] login  in
            self?.loginButton.enabled = true

            guard login?.result == 1 else {
                self?.showErrorWithMessage(login!.message)
                return;
            }

           // Logged in!
        })
        .addDisposableTo(disposeBag)

Looks fine, but if the login failed because of credential not valid and the user restart typing on to the text field the call to loginModel.login() is fired again...

Inside the view model there is also:

    var credentials : Driver<(String, String)> {
    return Driver.combineLatest(userNameDriver.distinctUntilChanged(), passwordDriver.distinctUntilChanged()) { usr, pwd in
        return (usr, pwd)
    }
}

var usrValid : Driver<Bool> {
    get {
        return userNameDriver
            .throttle(0.5)
            .filterEmpty()
            .distinctUntilChanged()
            .map { ($0.rangeOfString("@") != nil) || ($0.utf8.count == 0) }
    }
}

var pwdValid : Driver<Bool> {
    get {
        return passwordDriver
        .throttle(0.5)
        .filterEmpty()
        .distinctUntilChanged()
        .map { ($0.utf8.count > 5) || ($0.utf8.count == 0) }
    }
}

var usernameBorderColor : Observable<UIColor>!
var passwordBorderColor : Observable<UIColor>!

var credentialValid : Driver<Bool> {
    return Driver.combineLatest(usrValid, pwdValid) { usr, pwd in
        return (usr && pwd)
    }
}

Someone can help me with this? Thanks

Upvotes: 4

Views: 1957

Answers (1)

Svyatoslav
Svyatoslav

Reputation: 1360

I reviewed your project. The problem was not a very good architecture of your ViewModel. I fix one using example from RxSwift repo. See your new view model below:

class LoginViewModel
{
let validatedUsername: Driver<Bool>
let validatedPassword: Driver<Bool>

// Is login button enabled
let loginEnabled: Observable<Bool>

// Has user log in
let loggedIn: Observable<Login>

var usernameBorderColor : Observable<UIColor>
var passwordBorderColor : Observable<UIColor>

init(input: (
    userName: Driver<String>,
    password: Driver<String>,
    loginClick: Observable<Void>
    ),
    dependency: (
    RxMoyaProvider<APIProvider>
    )
    ) {

    let credentials = Driver.combineLatest(input.userName, input.password) { ($0, $1) }

    validatedUsername = input.userName.map { $0.rangeOfString("@") != nil }
    validatedPassword = input.password.map { $0.utf8.count > 5 }

    usernameBorderColor = validatedUsername.asObservable()
        .map{valid in
            return valid ? UIColor.clearColor() : UIColor.redColor()
    }
    passwordBorderColor = validatedPassword.asObservable()
        .map{valid in
            return valid ? UIColor.clearColor() : UIColor.redColor()
    }

    loginEnabled = Observable.combineLatest(
        validatedUsername.asObservable(),
        validatedPassword.asObservable()
    )   { username, password in
        username &&
        password
        }
        .distinctUntilChanged()
        .shareReplay(1)

    loggedIn = input.loginClick.withLatestFrom(credentials)
        .flatMap { username, password -> Observable<Login> in
            var resultLogin : Login
            if username == "test@" && password == "123123" {
                resultLogin = Login(result: 1, message: "OK")
            }
            else {
                resultLogin = Login(result: 0, message: "KO")
            }

            return Observable.create { observer in
                observer.onNext(resultLogin)
                observer.onCompleted()

                return AnonymousDisposable {

                }
            }
        }
        .retry()
        .shareReplay(1)
    }
}

Also you have to change your login view controller:

let viewModel = LoginViewModel(
        input: (
            userName: userTextField.rx_text.asDriver(),
            password: pwdTextField.rx_text.asDriver(),
            loginClick: loginButton.rx_tap.asObservable()
        ),
        dependency: (
            apiProvider
        )
    )

    viewModel.loginEnabled
        .subscribeNext { [weak self] valid  in
            self?.loginButton.enabled = valid
            self?.loginButton.alpha = valid ? 1.0 : 0.5
        }
        .addDisposableTo(self.disposeBag)

    viewModel.loggedIn
        .subscribeNext { login  in

            print(login.message)

            guard login.result == 1 else {
                // Show error
                return;
            }


            }
        .addDisposableTo(disposeBag)

I created pull request, you have to accept it and see full source code. Good luck!

Upvotes: 3

Related Questions