Login page using RxSwift in MVVM I am building a simple login page which will check for username before navigating to another screen and here is how I am doing the binding now. I would like to know if I am doing it right and if I am not, what is the recommended way of doing the binding. Moreover, I creating the UI programmatically.

ViewModel.swift

// Inputs
private let usernameSubject = PublishSubject&lt;String&gt;()
private let nextButtonDidTapSubject = PublishSubject&lt;Void&gt;()

// Outputs
private let validUsernameSubject = PublishSubject&lt;Bool&gt;()
private let invalidUsernameSubject = PublishSubject&lt;String&gt;()

// MARK: - Init

init() {

    input = Input(username: usernameSubject.asObserver(),
                  nextButtonDidTap: nextButtonDidTapSubject.asObserver())

    output = Output(validUsername: validUsernameSubject.asObservable(),
                    invalidUsername: invalidUsernameSubject.asObservable())

    nextButtonDidTapSubject
        .withLatestFrom(usernameSubject.asObservable())
        .subscribe(onNext: { [unowned self] text in
            if text.count &gt;= self.minUsernameLength {
                self.validUsernameSubject.onNext(true)
            } else {
                let message = text.count &gt; 0 ?
                    "Please enter a valid username" :
                    "Please enter a username"
                self.invalidUsernameSubject.onNext(message)
            }
        })
        .disposed(by: disposeBag)

}


ViewController.swift

private func configureBinding() {

    loginLandingView.usernameTextField.rx.text.orEmpty
        .subscribe(viewModel.input.username)
        .disposed(by: disposeBag)

    loginLandingView.nextButton.rx.tap
        .subscribe(viewModel.input.nextButtonDidTap)
        .disposed(by: disposeBag)

    viewModel.output.validUsername
        .subscribe(onNext: { [unowned self] _ in
            print("Valid username - Navigate...")
            self.navigate()
        })
        .disposed(by: disposeBag)

    viewModel.output.invalidUsername
        .subscribe(onNext: { [unowned self] message in
            self.showAlert(with: message)
        })
        .disposed(by: disposeBag)

}
I do not recommend this approach. There are entirely too many subjects and they are completely unnecessary. Too much boilerplate.

I recommend a more functional approach:

The view controller would contain something like this:

// assign viewModel before presenting.
var viewModel: (LoginInputs) -&gt; LoginOutputs = { _ in fatalError("assign before view is loaded.") }

// called from viewDidLoad()
private func configureBinding() {

    let inputs = LoginInputs(
        username: loginLandingView.usernameTextField.rx.text.orEmpty.asObservable(),
        loginTrigger: loginLandingView.nextButton.rx.tap.asObservable()
    )

    let outputs = viewModel(inputs)

    outputs.navigateTrigger
        .subscribe(onNext: { [unowned self] in
            self.navigate()
        })
        .disposed(by: disposeBag)

    outputs.invalid
        .subscribe(onNext: { [unowned self] message in
            self.showAlert(with: message)
        })
        .disposed(by: disposeBag)
}


And the view model would look like:

struct LoginInputs {
    let username: Observable&lt;String&gt;
    let loginTrigger: Observable&lt;Void&gt;
}

struct LoginOutputs {
    let navigateTrigger: Observable&lt;Void&gt;
    let invalid: Observable&lt;String&gt;
}

func loginViewModel(minUsernameLength: Int) -&gt; (_ inputs: LoginInputs) -&gt; LoginOutputs {
    return { inputs in
        let usernameEntered = inputs.loginTrigger
            .withLatestFrom(inputs.username)

        let navigateTrigger = usernameEntered
            .filter { minUsernameLength &lt;= $0.count }
            .map { _ in }
        let usernameTooShort = usernameEntered
            .filter { 1 &lt;= $0.count &amp;&amp; $0.count &lt; minUsernameLength }
            .map { _ in "Please enter a valid username" }
        let usernameEmpty = usernameEntered
            .filter { $0.isEmpty }
            .map { _ in "Please enter a username" }

        return LoginOutputs(
            navigateTrigger: navigateTrigger,
            invalid: Observable.merge(usernameTooShort, usernameEmpty)
        )
    }
}


The code that presents the view controller would look something like:

let controller = LoginViewController() 
controller.viewModel = loginViewModel(minUsernameLength: 8) // or whatever the minimum is.
// show the view controller


The above will maximize the testability of your code. You can test the view model by simply calling the function and pushing data into it. You can test the view controller by assigning a viewModel function that pushes test data.

The above will also establish a strong separation between your logic (in the view model) and your effects (in the view controller.)