5

I am learning Viper w/ RxSwift.

I would like to notify my Presenter that viewDidLoad was called in my ViewController.

To do this I have the following:

class LoginPresenter {

    weak var view: LoginView?
    var interactor: LoginUseCase?
    var router: LoginRouter?

    private(set) var viewDidLoad = PublishSubject<Void>()

    private lazy var disposeBag = DisposeBag()

    required init(view: LoginView?, interactor: LoginUseCase?, router: LoginRouter?) {
        self.view = view
        self.interactor = interactor
        self.router = router

        viewDidLoad
            .subscribe(onNext: { _ in
                // do something on viewDidLoad
            }).disposed(by: disposeBag)
    }
}
class LoginViewController: UIViewController {

    var presenter: LoginPresenter?

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter?.viewDidLoad.onNext(())
    }
}

Once my view is loaded I am calling presenter?.viewDidLoad.onNext(())

I am then able to trigger any actions within my presenter, such as calling out to my router to ensure navigation is configured or my interactor.

Should I be using a PublishSubject for this? Or does RxSwift have a better suited type?

I feel like this approach means I will end up with something like

        viewDidLoad
            .subscribe(onNext: { _ in
                self.router?.viewDidLoad.onNext(())
            }).disposed(by: disposeBag)
Andreas ZUERCHER
  • 862
  • 1
  • 7
  • 20
Tim J
  • 1,211
  • 1
  • 14
  • 31

1 Answers1

2

Hmm... A Presenter's job is to gather up user actions and I'm not so sure we should consider viewDidLoad a user action. And in any case, the Wireframe (which handles routing) shouldn't need to know when viewDidLoad is called in the first place; its job is to present new screens and you can't present a screen in viewDidLoad.

That said, you can setup your connection in the ViewController's presenter didSet:

final class ViewController: UIViewController {

    var presenter: Presenter? {
        didSet {
            guard let presenter = presenter else { viewDidLoadDisposable.dispose(); return }
            viewDidLoadDisposable.disposable = rx.methodInvoked(#selector(viewDidLoad))
                .map { _ in }
                .bind(to: presenter.viewDidLoad)
        }
    }

    let viewDidLoadDisposable = SerialDisposable()

    deinit {
        viewDidLoadDisposable.dispose()
    }
}

final class Presenter {
    let viewDidLoad = PublishSubject<Void>()
}

In general though, it is in the viewDidLoad where the presenter and viewController elements are normally bound together so the above code has a pretty unnatural feel.

Also, Observables, Subjects and the DisposeBag should not be vars, use lets instead. That's the "functional" part of functional reactive programming.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • I see thank you, I was using the `viewDidLoad` as a way to notify my `Presenter` to fetch initial data. I see how that is wrong and I appreciate your advice. I'll revisit my approach. – Tim J Sep 12 '19 at 07:23
  • 1
    A couple of points... (1) In VIPER the presenter shouldn't be doing the fetch, instead it should be starting up an interactor that takes care of that. (2) That's an initialization issue that the presenter can take care of independently of the view. In my experience, though the data should be re-fetched every time the view appears because you never know if the data was changed through some other source. And having the view notify the presenter every time it appears is likely more appropriate. Setting up that notification in the viewDidLoad is the typical case IMO. – Daniel T. Sep 12 '19 at 11:50