Network error handling combined with binding to tableView (Moya, RxSwift, RxCocoa)

I am currently using Moya to fulfill my network requests. I implemented the following from one of the sample projects @ https://github.com/DroidsOnRoids/RxSwiftExamples#tutorials

Below I set up restaurantSearch so that when someone enters text, he makes a new request.

var restaurantSearch: Observable<(String)> { return searchBar .rx_text .throttle(0.5, scheduler: MainScheduler.instance) .distinctUntilChanged() } 

I have a method that returns the observable type [Restaurant]

 func restaurants() -> Observable<[Restaurant]> { return restaurantSearch .observeOn(MainScheduler.instance) .flatMapLatest { postcode -> Observable<[Restaurant]?> in return self.getRestaurants(postcode, cuisine: "", restaurantName: "") }.replaceNilWith([]) } internal func getRestaurants(postcode: String, cuisine: String, restaurantName: String) -> Observable<[Restaurant]?> { return self.justEatProvider .request(.Restaurant(postcode, cuisine, restaurantName)) .debug() .mapArrayOptional(Restaurant.self, keyPath: "Restaurants") } 

I call this method and bind it to the tableView as follows:

 func setupRx() { api = JustEatApi(provider: provider, restaurantSearch: restaurantSearch) api .restaurants() .bindTo(tableView.rx_itemsWithCellIdentifier("RestaurantTableViewCell", cellType: RestaurantTableViewCell.self)) { (row, element, cell) in cell.restaurant = element } .addDisposableTo(disposeBag) } 

It works great. If I enter a zip code, it does a search and a tableView.

If I turn off the Internet and try to change the zip code, the View table will remain as it is. However, when I scroll through it, my application crashes with the following:

 @noreturn func rxFatalError(lastMessage: String) { // The temptation to comment this line is great, but please don't, it for your own good. The choice is yours. fatalError(lastMessage) } 

Also, if I don't scroll, but instead just go back to the Internet and change the zip code, nothing happens. It looks like he lost his binding.

At first I tried to add catchOnError before calling the bindTo method, but I read here in the comment that it cannot be processed as part of UIBinding: http://blog.scottlogic.com/2014/05/11/reactivecocoa-tableview-binding.html

I assume that I should process it in a method:

 func restaurants() -> Observable<[Restaurant]> { return restaurantSearch .observeOn(MainScheduler.instance) .flatMapLatest { postcode -> Observable<[Restaurant]?> in return self.getRestaurants(postcode, cuisine: "", restaurantName: "") }.replaceNilWith([]) } 

I have 2 questions:

1) Where and how should a network error be handled?

2) Why the tableView is not updated after I return to the Internet?

Any help is greatly appreciated.

+7
source share
2 answers

I personally handle network errors in services that make / analyze network requests. A good way to do this is to wrap the results of your network with some renaming (similar to optional, but with an error if nil). This way you will have something like:

 enum APIResult<T> { case Success(T) case Error(ErrorType) } 

And then your services will return something like this

 Observable<APIResult<[Restaurant]>> 

If you use the MVVM architecture, you should only filter successful results in the view model and provide this data to view the controller.

You should also take a look at the driver block. This device is specifically designed for bindings to the user interface, so it subscribes only to the Main stream, never leads to errors and gives the last result.

To translate Observable to Driver, you use one of three methods:

 func asDriver(onErrorJustReturn onErrorJustReturn: Self.E) -> RxCocoa.Driver<Self.E> func asDriver(onErrorDriveWith onErrorDriveWith: RxCocoa.Driver<Self.E>) -> RxCocoa.Driver<Self.E> func asDriver(onErrorRecover onErrorRecover: (error: ErrorType) -> RxCocoa.Driver<Self.E>) -> RxCocoa.Driver<Self.E> 

This approach will automatically solve your second question, because when the Observable emits an error, all subscribers are unsubscribed, i.e. he completes the stream. Try putting .debug () behind your api.restaurants () call and see for yourself that it is unsubscribing.

Read more about the driver and other devices here .

+10
source

Just add catchErrorJustReturn ([]) before binding the table.

0
source

All Articles