How to combine two results from the different subjects and use latest one's result

475 Views Asked by At

I am having two view models. One for single contact management, other for whole contact list management.

And I have errors for both of them that can happen. In both cases I should apply same action - to show the error. But how I would do this more elegantly, so that every time, no matter from which view model error came, to show it only based on which error came the last?

I have this code right now:

 private func observeErrors(){
        
        let popup = PopupViewController.instantiate()
        let popupActionHandler =  {
            popup.dismiss(animated: true, completion: nil)
        }
        
        contactsViewModel.error.subscribe(onNext: { error in
            print(error.localizedDescription)
            switch error {
        
            case .unknown:
                self.showPopup(popup: popup, popupTitle: "An unknown error occured".localized, popupMessage: "Please try again.".localized, buttonTitle: nil, actionHandler: popupActionHandler)
            case .serverResponse(let message):
                
                self.showPopup(popup: popup, popupTitle: "An error occured".localized, popupMessage: message, buttonTitle: nil, actionHandler: popupActionHandler)
                
            }
        }).disposed(by: disposeBag)
        
        contactViewModel.error.subscribe(onNext: { error in
            print(error.localizedDescription)
            switch error {
           
            case .unknown:
                self.showPopup(popup: popup, popupTitle: "An unknown error occured".localized, popupMessage: "Please try again.".localized, buttonTitle: nil, actionHandler: popupActionHandler)
            case .serverResponse(let message):
                
                self.showPopup(popup: popup, popupTitle: "An error occured".localized, popupMessage: message, buttonTitle: nil, actionHandler: popupActionHandler)
                
            }
        }).disposed(by: disposeBag)
    }

but this is duplicating. I tried with combineLatest, but I am not sure how determine what was the last error that occurred and to show only that?

3

There are 3 best solutions below

2
Rico Crescenzio On BEST ANSWER

I guess you can use

Observable.merge

So it takes n observables (which must have same element type) and attach single subscriber to both. They are merged into a single flow:

Observable.merge(contactsViewModel.error, contactViewModel.error)
    .subscribe(onNext: { error in 
        // your logic for both observables
    })
    .disposed(by: disposeBag)
1
Rajendra On

There are two ways I as per me, 1st,

private func observeErrors(){
    
    let popup = PopupViewController.instantiate()
    let popupActionHandler =  {
        popup.dismiss(animated: true, completion: nil)
    }
    var contactVM = (contactsViewModel!= null)? contactsViewModel : contactViewModel
    contactVM.error.subscribe(onNext: { error in
        print(error.localizedDescription)
        switch error {
    
        case .unknown:
            self.showPopup(popup: popup, popupTitle: "An unknown error occured".localized, popupMessage: "Please try again.".localized, buttonTitle: nil, actionHandler: popupActionHandler)
        case .serverResponse(let message):
            
            self.showPopup(popup: popup, popupTitle: "An error occured".localized, popupMessage: message, buttonTitle: nil, actionHandler: popupActionHandler)
            
        }
    }).disposed(by: disposeBag)
}

2nd way, Using generic.

Note: I'm iOS developer. I'm in good skilled Objective-C developer, I don't have much experience in swift syntax's, so my written answer might be chances to the wrong syntax

1
Daniel T. On

@RicoCrescenzio is correct; use Observable.merge. That said, you made a comment about only showing the "last" error. By that I assume you mean the most recent. As in, if one of the observables emits an error while a popup is being displayed, then you want to dismiss the current popup and display the new one (even if the user didn't tap the button.) Doing that is a bit more complex and frankly is probably not necessary but I don't know the details of your use case.

To make this easier, I'm using my CLE library. The code below will display a UIAlertController with the title and message when either of the error observables emits a value. If an alert from a previous error is currently being displayed, it will dismiss that alert before displaying the new one.

// only call this once in viewDidLoad
private func observeErrors() {
    Observable.merge(contactsViewModel.error, contactViewModel.error)
        .map { (error) -> (title: String, message: String) in
            switch error {
            case .unknown:
                return (title: "An unknown error occured".localized, message: "Please try again.".localized)
            case let .serverResponse(message):
                return (title: "An error occured".localized, message: message)
            }
        }
        .flatMapLatest(presentScene(animated: true, scene: { title, message in
            UIAlertController(title: title, message: message, preferredStyle: .alert)
                .scene { $0.connectOK() }
        }))
        .subscribe()
        .disposed(by: disposeBag)
}