smandrus
smandrus

Reputation: 335

RxSwfit: Is it bad practice to subscribe to a presented view controllers view model?

I am relatively new to RxSwift and am trying to implement best practices as I develop.

On my home view controller, I have to present a custom alert view controller where the user enters text into a textfield and taps confirm. Assuming the text is valid, the alert is dismissed and a new view controller is pushed.

To avoid using a callback or delegate, I present the alert view controller, then my home view controller subscribes to the alert view controller's textfield and confirm button.

Is it bad practice to subscribe to a different view controller?

Pseudocode:

    let alert = viewModel.textFieldAlert()
    present(alert)
    alertSubscriptions(alert)

alertSubscriptions:

    alert.textField.rx.text.subscribe(onNext: { [weak self] text in
        self?.viewModel.numberOfItems.value = text ?? ""
    }).addDisposableTo(disposeBag)

    alert.confirmButton.rx.tap.subscribe(onNext: { [weak self] _ in
        guard self != nil else { return }
        if !self!.viewModel.validText { return }
        alert.dismiss()
        self!.alertConfirmed()
    }).addDisposableTo(disposeBag)

I have tested this code and it works without any problems.

Upvotes: 1

Views: 1850

Answers (1)

Daniel T.
Daniel T.

Reputation: 33979

I happen to have written an article about this very subject: https://medium.com/@danielt1263/encapsulating-the-user-in-a-function-ec5e5c02045f The article uses Promises, but the same procedure would, and IMHO should, be done when using Rx.

I think something like this would be better:

extension UIViewController {

    func infoAlert(title: String, message: String, isValidInput: @escaping (String) -> Bool) -> Observable<String> {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        let confirm = PublishSubject<Void>()
        let confirmed = UIAlertAction(title: "OK", style: .default) { (action) in
            confirm.onNext()
        }

        let text = PublishSubject<String>()
        alert.addTextField { textField in
            textField.placeholder = "Enter Data"
            _ = textField.rx.text.orEmpty.bind(to: text)
        }

        _ = text.map(isValidInput)
            .bind(to: confirmed.rx.isEnabled)

        alert.addAction(confirmed)

        present(alert, animated: true, completion: nil)
        return confirm.withLatestFrom(text)
    }
}

By containing all the code in a sequence emitter function (i.e., a function that returns an observable,) you open the door for adding the function to a chain of observables.

For example, the function above could be flatMapped off of a button press in your view controller, or otherwise added to a more involved Observable pipeline.

Upvotes: 2

Related Questions