1
votes

I want to use catchError for getting back my error as custom type. At first, I want my network layer return Observable and then in ViewModel I subscribed it for .OnNext, .OnError, .OnCompleted events, But I don't know how should I handle Errors such as 4xx, 5xx network status code and then, them return my Custom Error Object!

My Login ViewModel :

func getAccessToken() {        
        let network = NetworkRequest()

        network.logInRequest(tokenType: .guest, token: "cce577f6021608", secondKey: "09128147040", client: "cce577f6021608bc31424d209cbf5120c3683191").subscribe(onNext: { loginData in
            self.token.onNext(loginData.access_token)
        }, onError: { error in
            print("The Error is: \(error.localizedDescription)")
        }, onCompleted: {
            print("Its Completed")
        }).disposed(by: bag)
    }

My network layer function:

class NetworkRequest: NSObject {
    var rxProvider: MoyaProvider<WebServiceAPIs>

    override init() {
        rxProvider = MoyaProvider<WebServiceAPIs>( plugins: [ NetworkLoggerPlugin(verbose:true) ])
    }

    func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
        return rxProvider.rx
              .request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
              .filterSuccessfulStatusCodes()
              .catchError({ error -> Observable<NetworkError> in
                return //Observable.just() => I want to return a custom network error as obserable 
              })
              .map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
    }

}

thanks for any help

2

2 Answers

0
votes

In my experience, '.materialize()' operator is the perfect solution for handling HTTP errors. Instead of separate events for success and error you get one single wrapper event with either success or error nested in it.

0
votes

Moya returns MoyaError enum in error block which you can handle by extracting the error type using switch on MoyaError and then using statusCode to convert to NetworkError enum

func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
        return sharedProvider.rx
                .request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
                .filterSuccessfulStatusCodes()
                .catchError({ [weak self] error -> Observable<NetworkError> in
                    guard let strongSelf = self else { return Observable.empty() }
                    if let moyaError = error as? MoyaError {
                        let networkError = self?.createNetworkError(from: moyaError)
                        return Observable.error(networkError)
                    } else {
                        return Observable.error(NetworkError.somethingWentWrong(error.localizedDescription))
                    }
                })
                .map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
    }

    func createNetworkError(from moyaError: MoyaError) -> NetowrkError {
        switch moyaError {
        case .statusCode(let response):
            return NetworkError.mapError(statusCode: response.statusCode)
        case .underlying(let error, let response):
            if let response = response {
                return NetworkError.mapError(statusCode: response.statusCode)
            } else {
                if let nsError = error as? NSError {
                  return NetworkError.mapError(statusCode: nsError.code)
                } else {
                  return NetworkError.notConnectedToInternet
                }
            }
         default:
              return NetworkError.somethingWentWrong("Something went wrong. Please try again.")
        }
    }

You can create your custom NetworkError enum like below which will map statusCode to custom NetworkError enum value. It will have errorDescription var which will return custom description to show in error view

enum NetworkError: Swift.Error {
    case unauthorized
    case serviceNotAvailable
    case notConnectedToInternet
    case somethingWentWrong(String)

    static func mapError(statusCode: Int) -> NetworkError {
        switch statusCode {
        case 401:
            return .unauthorized
        case 501:
            return .serviceNotAvailable
        case -1009:
            return .notConnectedToInternet
        default:
            return .somethingWentWrong("Something went wrong. Please try again.")
        }
    }

    var errorDescription: String {
        switch self {
        case .unauthorized:
            return "Unauthorised response from the server"
        case .notConnectedToInternet:
            return "Not connected to Internet"
        case .serviceNotAvailable:
            return "Service is not available. Try later"
        case .somethingWentWrong(let errorMessage):
            return errorMessage
        }
    }
}