Karl-John Chow
Karl-John Chow

Reputation: 805

RxSwift Make Observable trigger a subject

I have a BehaviorSubject where my tableview is bound to through RxDataSources.

Besides that, I have a pull to refresh which creates an observable that updates the data and updates the data in the BehaviorSubject so that my UITableView updates correctly.

Now the question is, how do I handle the error handling for whenever my API call fails?

Few options that I have thought of was:

  1. Subscribe to the observer's onError and call the onError of my BehaviorSubject\
  2. Somehow try to concat? or bind(to: ..)
  3. Let another subscriber in my ViewController subscribe besides that my tableview subscribes to the BehaviorSubject.

Any suggestions?

Upvotes: 2

Views: 1098

Answers (1)

Daniel T.
Daniel T.

Reputation: 33967

Ideally, you wouldn't use the BehaviorSubject at all. From the Intro to Rx book:

The usage of subjects should largely remain in the realms of samples and testing. Subjects are a great way to get started with Rx. They reduce the learning curve for new developers, however they pose several concerns...

Better would be to do something like this in your viewDidLoad (or a function that is called from your viewDidLoad):

let earthquakeData = Observable.merge(
    tableView.refreshControl!.rx.controlEvent(.valueChanged).asObservable(),
    rx.methodInvoked(#selector(UIViewController.viewDidAppear(_:))).map { _ in }
)
    .map { earthquakeSummary /* generate URLRequest */ }
    .flatMapLatest { request in
        URLSession.shared.rx.data(request: request)
            .materialize()
    }
    .share(replay: 1)

earthquakeData
    .compactMap { $0.element }
    .map { Earthquake.earthquakes(from: $0) }
    .map { $0.map { EarthquakeCellDisplay(earthquake: $0) } }
    .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: EarthquakeTableViewCell.self)) { _, element, cell in
        cell.placeLabel.text = element.place
        cell.dateLabel.text = element.date
        cell.magnitudeLabel.text = element.magnitude
        cell.magnitudeImageView.image = element.imageName.isEmpty ? UIImage() : UIImage(named: element.imageName)
    }
    .disposed(by: disposeBag)

earthquakeData
    .compactMap { $0.error }
    .map { (title: "Error", message: $0.localizedDescription) }
    .bind { [weak self] title, message in
        self?.presentAlert(title: title, message: message, animated: true)
    }
    .disposed(by: disposeBag)

The materialize() operator turns a Event.error(Error) result into an Event.next(.error(Error)) so that the chain won't be broken down. The .compactMap { $0.element } emits only the successful results while the .compactMap { $0.error } emits only the errors.

The above code is adapted from my RxEarthquake sample.

Upvotes: 3

Related Questions