Shabir jan
Shabir jan

Reputation: 2427

RxSwift - Button Tap using Publish Subject

So I have a button inside my ViewController which is connected to ViewModel and than whenever the button is tapped, in my coordinator I navigate to another screen. The code is like this:

VC

btnShowShopsMap.rx.tap
            .bind(to: viewModel.selectShowMap)

VM

let selectShowMap: AnyObserver<Void>
    let showShopMap: Observable<Void>
//Inside init
 let _selectShowMap = PublishSubject<Void>()
        selectShowMap = _selectShowMap.asObserver()
        showShopMap = _selectShowMap.asObservable()

Coordinator

viewModel.showShopMap
            .subscribe(onNext: { _ in self.showShopMap()})
            .disposed(by: userShopVC.disposeBag)

Is it possible to refactor above code? rather than using PublishSubject is there any other way to do what i am doing using PublishSubject

My VC, VM & Coordinator Flow

Coordinator

func showLoginScreen(logout: Bool = false) {

    guard let viewController = LoginViewController.instantiate(storyboard: .main) else { return }

    viewController.viewModelFactory = { inputs in
        let viewModel = LoginViewModel(inputs: inputs)
        viewModel.showHome
            .subscribe(onNext: {  isLogged in
                if isLogged {
                    self.showHomeScreen()
                }
            })
            .disposed(by: viewController.disposeBag)

        inputs.showOnboarding
            .subscribe(onNext: { _ in
                self.showOnboardingScreen()
            })
            .disposed(by: viewController.disposeBag)
        return viewModel
    }
navController.pushViewController(viewController, animated: true)

VC

var viewModelFactory: (LoginViewModel.UIInputs) -> LoginViewModel = { _ in fatalError("factory not set")}

let inputs = LoginViewModel.UIInputs(userNumber: txtUserNumber.rx.text.orEmpty.asDriver(),
                                             password: txtPassword.rx.text.orEmpty.asDriver(),
                                             loginTapped: btnLogin.rx.tap.asSignal(),
                                             userNumberLostFocus: txtUserNumber.rx.controlEvent(.editingDidEnd).asSignal(),
                                             passwordLostFocus: txtPassword.rx.controlEvent(.editingDidEnd).asSignal(),
                                             indicator: indicator,
                                             showOnboarding: btnShowOnboarding.rx.tap.asObservable())

VM

struct  UIInputs {
    let userNumber: Driver<String>
    let password: Driver<String>
    let loginTapped: Signal<Void>
    let userNumberLostFocus: Signal<Void>
    let passwordLostFocus: Signal<Void>
    let indicator: ActivityIndicator
    let showOnboarding: Observable<Void>
}
 init(inputs: UIInputs) {}

Upvotes: 1

Views: 2861

Answers (2)

Shabir jan
Shabir jan

Reputation: 2427

I found very easy and simple way to solve my issue and avoid using Subject, As there was no logic related to my button in VM, I don't need pass my Button tap to my VM either by using Observable or using Subject. Instead I directly accessed my button in my Coordinator like this:

viewController.btnShowOnboarding.rx.tap
      .subscribe(onNext: { _ in
         self.showOnboardingScreen()
      })
     .disposed(by: viewController.disposeBag)

Upvotes: 0

fphilipe
fphilipe

Reputation: 10056

Assuming the view controller owns and instantiates the view model, you could pass the tap control event as an observable to the view model initializer, which then exposes it as an observable for the coordinator to subscribe to:

// VC:
let viewModel = ViewModel(..., showShopMap: btnShowShopMap.rx.tap.asObservable())

// VM:
let showShopMap: Observable<Void>

init(..., showShopMap: Observable<Void>) {
    self.showShopMap = showShopMap
}

I try not to use subjects whenever possible and instead just expose transformed observables that were passed in.

Upvotes: 1

Related Questions