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.