Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return state from .submitting state doesn't work #10

Open
Sadmansamee opened this issue Jul 23, 2021 · 0 comments
Open

return state from .submitting state doesn't work #10

Sadmansamee opened this issue Jul 23, 2021 · 0 comments

Comments

@Sadmansamee
Copy link

Hi @V8tr
I'm returning a state on .submitting state for a particular event, but it doesn't return the expected state, here's my code, what am I doing wrong here?

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)

Here's the shortcode

        case .submitting:
            switch event {
            case let .onFailedToLogin(error):
                return .error(error)
            case let .onSignedIn(token):
                return .signedIn(token)
            default:
                return state
            }

Here's the full code

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 }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant