here on .submitting state from an API call I'm calling onSignedIn event, but even after calling return .signedIn(token) , II am not getting this state case let .signedIn(token)
case .submitting:
switch event {
case let .onFailedToLogin(error):
return .error(error)
case let .onSignedIn(token):
return .signedIn(token)
default:
return state
}
class LoginViewModel: LoginViewModelProtocol, Identifiable {
let authRepository: AuthRepository
@Published var phoneNumber: String = ""
@Published var password: String = ""
@Published var isValid: Bool = false
@Published var phoneNumberError: String?
@Published var passwordError: String?
private var bag = Set<AnyCancellable>()
private let input = PassthroughSubject<Event, Never>()
@Published private(set) var state = State.initial
@Published var phoneNumberValidator = PhoneNumberValidation.empty
@Published var passwordValidator = PasswordValidation.empty
@Inject var router: Router
init() {
authRepository = AuthRepository()
Publishers.system(
initial: state,
reduce: reduce,
scheduler: RunLoop.main,
feedbacks: [
whenSubmitting(),
Self.userInput(input: input.eraseToAnyPublisher())
]
)
.assign(to: \.state, on: self)
.store(in: &bag)
Publishers.CombineLatest(validPhoneNumberPublisher, passwordValidatorPublisher)
.dropFirst()
.sink { _emailError, _passwordValidator in
self.isValid = _emailError.errorMessage == nil &&
_passwordValidator.errorMessage == nil
if self.isValid {
self.send(event: .onSubmitable)
}
print("LoginViewModel combine \(self.state)")
}
.store(in: &bag)
validPhoneNumberPublisher
.dropFirst()
.sink { _error in
self.phoneNumberError = _error.errorMessage
}
.store(in: &bag)
passwordValidatorPublisher
.dropFirst()
.sink { _error in
self.passwordError = _error.errorMessage
}
.store(in: &bag)
}
deinit {
bag.removeAll()
}
func send(event: Event) {
input.send(event)
}
private var validPhoneNumberPublisher: AnyPublisher<PhoneNumberValidation, Never> {
$phoneNumber
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
.map { _value in
if _value.isEmpty {
return .empty
} else if !_value.isPhoneNumber() {
return .inValidPhoneNumber
} else {
return .validPhoneNumber
}
}
.eraseToAnyPublisher()
}
private var passwordValidatorPublisher: AnyPublisher<PasswordValidation, Never> {
$password
.removeDuplicates()
.debounce(for: 0.5, scheduler: RunLoop.main)
.map { password in
if password.isEmpty {
return .empty
} else {
return passwordStrengthChecker(forPassword: password)
}
}
.eraseToAnyPublisher()
}
}
// MARK: - Inner Types
extension LoginViewModel {
enum State {
case initial
case submitable
case submitting
case signedIn(OauthToken)
case error(HttpError)
}
enum Event {
case onStart
case login
case onSubmitable
case onSignedIn(OauthToken)
case onFailedToLogin(HttpError)
}
}
// MARK: - State Machine
extension LoginViewModel {
private func handleError(error: HttpError) {
switch error {
case let .errors(apiError):
if let errors = apiError.errors {
for error in errors {
if error.key == "invalid_resource_owner_password" {
passwordError = error.message
}
}
}
default:
break
}
}
func reduce(_ state: LoginViewModel.State, _ event: LoginViewModel.Event) -> LoginViewModel.State {
switch state {
case .initial:
switch event {
case .onSubmitable:
return .submitable
default:
return state
}
case .submitting:
switch event {
case let .onFailedToLogin(error):
handleError(error: error)
return .error(error)
case let .onSignedIn(token):
return .signedIn(token)
default:
return state
}
case let .signedIn(token):
return state
case let .error(error):
handleError(error: error)
return state
case .submitable:
switch event {
case .login:
return .submitting
default:
return state
}
}
}
func whenSubmitting() -> Feedback<State, Event> {
Feedback { (state: State) -> AnyPublisher<Event, Never> in
guard case .submitting = state else { return Empty().eraseToAnyPublisher() }
return self.authRepository
.login(phoneNumber: self.phoneNumber, password: self.password)
.map(Event.onSignedIn)
.catch { Just(Event.onFailedToLogin($0)) }
.eraseToAnyPublisher()
}
}
static func userInput(input: AnyPublisher<Event, Never>) -> Feedback<State, Event> {
Feedback { _ in input }
}
}