I have a login screen, I was showing previously error input above textfield and disable button by combining email/password Bool Observable, however now the design is changed and I want to check if email/password are not empty before sending the request or show alert dialog
My issue is with how to validate inputs are valid before hitting the request:
LoginViewModel:
var emailValid: Observable<Bool> {
return emailSubject.asObservable().map { $0.count > 0 && $0.isEmail}
}
signInDidTapSubject
.withLatestFrom(credentialsObservable)
.flatMapLatest { credentials -> Observable<Event<Result<UserResponse>>> in
self.loadInProgress.accept(true)
return network.login(with: credentials).materialize()
}
.subscribe(onNext: { [weak self] event in
self?.loadInProgress.accept(false)
switch event {
case .next(let result):
switch result{
case .Success(let user):
self?.loginResultSubject.onNext(user)
case .Failure(let error):
self?.errorsSubject.onNext(error)
}
case .error( _):
print("error")
default:
break
}
})
.disposed(by: disposeBag)
VC:
loginButton.rx.tap.asObservable()
.debounce(0.5, scheduler: MainScheduler.instance)
.subscribe(viewModel.input.loginButtonTapped).disposed(by: disposeBag)
The way you figure this out is by asking yourself, "What inputs do I need to produce this output?"
You have two outputs here "User" and "Error". Let's do the happy path first. What inputs do you need to produce a User? Just a successful network response. But what inputs do you need to produce a network response? Well you need the email and password, but the inciting incident is tapping on the login button and the request shouldn't be made if the data isn't valid:
let networkResponse = loginTrigger
.withLatestFrom(credentials)
.filter { $0.email.isEmail && !$0.password.isEmpty }
.flatMapLatest {
network.login(with: $0)
.materialize()
}
.share(replay: 1)
let user = networkResponse
.filter { $0.element != nil }
.map { $0.element! }
Now, what inputs do you need to make the Error output? You need the error from the network request if there is one, and you need to know if the email or password were invalid. However, you only want to know about invalid input if the login button is tapped...
That's a lot, but here's how it breaks down:
let networkError = networkResponse
.filter { $0.error != nil }
.map { $0.error! }
let invalidEmailError = loginTrigger
.withLatestFrom(email)
.filter { !$0.isEmail }
.map { _ in UIError.badEmail as Error }
let invalidPasswordError = loginTrigger
.withLatestFrom(password)
.filter { $0.isEmpty }
.map { _ in UIError.badPassword as Error }
let error = Observable.merge(networkError, invalidEmailError, invalidPasswordError)
See how that worked? Take each output in turn and figure out what inputs you need to make that output work.