简体   繁体   中英

Handling error with RxSwift and MVVM

I am trying to put together a nice architecture with RxSwift and MVVM. I am wondering how to handle the errors coming from observables correctly.

My ViewModel looks like:

class MapViewModel {

    private let disposeBag = DisposeBag()

    private let listObservable: Observable<[MyObject]>

    let list: Variable<[MyObject]>
    let showError: Variable<Bool>

    init() {
        self.listObservable = getObservable().shareReplay(1)

        self.list = Variable<[MyObject]>([])
        self.listObservable
            .bind(to: list)
            .addDisposableTo(self.disposeBag)

        self.showError = Variable<Bool>(false)
        self.listObservable
            .subscribe(
                onError: { [weak self] error in
                    print("Error downloading: \(error)")
                    self?.showError.value = true
            }).addDisposableTo(disposeBag)
    }
}

I thought it was to correct way to do it: clean separation of concerns, and shared observable to prevent calling the REST endpoint multiple times.

However when I do this - and the observable sends an error - I get a fatalError from RxSwift because the error is not handled in the bind() call.

To fix this, I changed the code to:

self.listObservable
    .subscribe(
        onNext: { [weak self] list in
            self?.list.value = list
        },
        onError: { [weak self] error in
            print("Error downloading: \(error)")
            self?.showError.value = true
    }).addDisposableTo(disposeBag)

This seems less clear for me. What should be the correct approach with this ?

The use of Variable s is throwing you off I think. Avoid them when possible.

My impression of this code:

  • When the view model is created, it makes a call getObservable() .

  • There is no provision for input from the user.

  • The showError variable seems to be getting used as a trigger rather than to actually pass a value so it should be Void instead of Bool

I would expect this view model to look more like:

struct MapViewModel {
    let list: Observable<[MyObject]>
    let showError: Observable<Void>

    init() {
        let dataResult = getObservable()
            .materialize()
            .shareReplayLatestWhileConnected()
        list = dataResult.map { $0.element }
            .filter { $0 != nil }
            .map { $0! }
        showError = dataResult.map { $0.error }
            .filter { $0 != nil }
            .map { _ in }
    }
}

The secret sauce in the above is the use of materialize to convert the error into an onNext event so you can use it for the trigger.

The subscribes/binds and dispose bag should be in the view controller, not in the view model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM