Handling Network Error in Combination with Binding to Tableview (Moya, Rxswift, Rxcocoa)

Handling Network error in combination with binding to tableView (Moya, RxSwift, RxCocoa)

I personally handle network errors in services that make/parse network requests. Good way to do that would be to wrap your network results in some kind of enum (similar to optional, but with error if nil). So you would have something like:

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

And then your services would return something like this

Observable<APIResult<[Restaurant]>>

If you use MVVM architecture you would then filter only successful results in view model and provide that data to view controller.

Also you should take a look at Driver unit. It is unit that is specifically made for UI bindings, so it subscribes on Main thread only, never errors and shares last result.

To translate Observable to Driver you use one of the 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 would automatically deal with your second question, because when Observable emits an error, all subscribers unsubscribe, ie. it terminates the stream. Try to put .debug() behind your api.restaurants() call and see by yourself that it unsubscribes.

You can read more about Driver and other units here

Handle Connection Error in UITableView Binding (Moya, RxSwift, RxCocoa)

You aren't handling errors anywhere. I mean you are acknowledging the error in the do operator but that doesn't actually handle it, that just allows it to pass through to the table view, which can't handle an error.

Look up the catchError series of operators for a solution. Probably .catchErrorJustReturn([]) will be all you need.


In a comment, you said:

... I don't want to return empty Array to my table. I want to show the error to customer and customer can retry service

In that case, you should use .catchError only for the success chain and setup a separate chain for the error as done below.

fileprivate func getNextState() {
showFullPageState(State.LOADING)
let products = viewModel.getProductListByID(orderGroup: OrderGroup.SERVICES.rawValue)
.share()

products
.catchError { _ in Observable.never() }
.filter { $0.products != nil }
.map { $0.products! }
.bind(to: tableView!.rx.items(cellIdentifier: cellIdentifier, cellType: ProductCell.self)) {
(row, element, cell) in
self.showFullPageState(State.CONTENT)
cell.product = element
}
.disposed(by: bag)

products
.subscribe(onError: { error in
showStatusError(error: error)
self.showFullPageState(State.CONTENT)
})
.disposed(by: bag)

self.tableView?.rx.setDelegate(self).disposed(by: bag)
}

The way you have the code setup, the only way for the user to retry the service is to call the function again. If you want to let the user retry in a more declarative manor, you would need to tie the chain to an observable that the user can trigger.

rxCocoa Binding error to UI: objectDeleted

I find out It's the changeObservable that emit an error when an object is deleted from the realm. Fix the error like this:

    lazy var isReaded: Driver<Bool> = {
guard let mail = StoreManager.mail(with: mailID) else {return Observable.empty().asDriver(onErrorJustReturn: false)}
let change = StoreManager.mailChange(with: mailID)

let o: Driver<Bool> = change
.filter({
let name: String = $0.name
return (name == "flags")

})
.map{ property in
let v = property.newValue! as! Int
return ((v & MCOMessageFlag.seen.rawValue) != 0)
}.startWith(mail.isReaded).asDriver(onErrorJustReturn: false)
return o
}()

catch an error and return a default value.

TableView made with RxSwift/RxCocoa glitches after a data change in the Variable it's bound to

To fix the issue you have to assign a new instance of DisposeBag to the cell's disposeBag variable when prepareForReuse() is called:

class CustomCell: UITableViewCell{

var disposeBag = DisposeBag()

override func prepareForReuse() {
super.prepareForReuse()

disposeBag = DisposeBag()
}
}

Memory Issue Possibly Related RxCocoa Binding Method

This has nothing to do with RxCocoa's binding method.

Your DataModel holds a UIImage. You are downloading the image for each cell and the caching the image in the DataModel. Therefore, your memory should increase as more cells are displayed. This would happen even if you weren't using Rx.

Updating Moya/RxSwift breaking my network calls

Seems the issue is retaining the MoyaProvider object… If I move the provider outside of the function and store it as a property in the class, then everything works properly.

class MyClass {
let provider = MoyaProvider<MyMoyaService>()
func getServerData(endpoint: MyMoyaService) -> Observable<Any> {
return provider.rx.request(endpoint)
.asObservable()
.filterSuccessfulStatusAndRedirectCodes()
.mapJSON()
.flatMap { response -> Observable<Any> in
return Observable(…)
}
}
}

From the providers documentation:

Always remember to retain your providers, as they will get deallocated if you fail to do so.

I suppose at some point they updated how these providers get deallocated?

Update SwiftUI List after network request using RxSwift

While I've found some Combine solution. Still waiting for Rx solution.
So, I fixed it just added @ObservedObject for ContactList property and @Published for users' array in Network model. Much less code, natively but not what I was looking for.

Full answer:

struct ContactList: View {
@ObservedObject var networkModel: NetworkModel
var body: some View {
NavigationView {
List(networkModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}

#if DEBUG
struct ContactList_Previews: PreviewProvider {
static var previews: some View {
ContactList(networkModel: .init(users: contactData))
}
}
#endif

class NetworkModel: ObservableObject {
@Published var users = [User]()

init(users: [User] = []) {
getMembers()
}

private func getMembers() {
let provider = MoyaProvider<TeamTarget>()
provider.request(.get) { [weak self] (result) in
switch result {
case .success(let response):
do {
let result = try JSONDecoder().decode([User].self, from: response.data)
self?.users.append(contentsOf: result)
} catch {
print(error.localizedDescription)
}
case .failure(let error):
print(error.errorDescription.orEmpty)
}
}
}
}

DZNEmptyDataSet not compatible with tableview binding in RxSwift. Was anyone able to make it work?

I had an issue where if the last item was deleted, the DZNEmptyDataSet wouldn't reappear. I was able to get it working by subclassing RxTableViewSectionedAnimatedDataSource

final class MyRxTableViewSectionedAnimatedDataSource<S: AnimatableSectionModelType>: RxTableViewSectionedAnimatedDataSource<S> {

private var currentItemsCount = 0

var isEmpty: Bool {
return currentItemsCount == 0
}

override func tableView(_ tableView: UITableView, observedEvent: Event<[S]>) {
super.tableView(tableView, observedEvent: observedEvent)
switch observedEvent {
case let .next(events):
guard let lastEvent = events.last else { return }
currentItemsCount = lastEvent.items.count
default: break
}
}
}

Then return the dataSource.isEmpty property in DZNEmptyDataSetDelegate

func emptyDataSetShouldDisplay(_ scrollView: UIScrollView!) -> Bool {
return dataSource.isEmpty
}

Originally, in the delegate method I was returning the number of rows for section 0. The delay between when the original DataSource sent deleted events and when the tableView registered the changes kept the number above zero when tableView.reloadEmptyDataSet() was called.

Now it is called automatically and is given the proper shouldDisplay value.



Related Topics



Leave a reply



Submit