Login page using RxSwift in MVVM
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.)