Reputation: 450
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
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