Note:

Network Error Handling

  • onResponse() returns a HTTP response (200, 400, 500 etc)
  • onFailure() represents a network error (like no internet) or an unexpected error

It can be tedious to implement both these callbacks for every Call that you make, especially if your error handling logic for an error response is the same as your error handling for a call failure.

Using a customCallAdapter, you could modify the callback for enqueue()so that all your error handling is done in the one callback:

I still don’t really enjoy having to implement multiple callbacks, so we can take advantage of Kotlin’s sealed classes, and migrate our code to a single callback for both success and error.

enqueue() now looks like:

Mapping Errors

In the project I’m working on, I have a Retrofit client called OAuthClient for dealing with authentication, and another client calledApiClient for calls to the backend. OAuthClient can return an OAuthError and ApiClient can return ProblemResponse errors which contain JSON describing what caused the error, so I have an Error class which models the JSON.

So, we can provide an error mapper to our custom CallAdapter. This allows us to define which kinds of errors the CallAdapter is aware of, which is useful because we apply the CallAdapter to a particular Retrofit client instance.

We’ll make theCallAdapter accept an optionalResponseErrorMapper closure:

responseErrorMapper: ((Response<*>) -> Error?)

It gets used in the CallAdapter like this:

Which basically reads “if our response was not successful, an error mapper was supplied, and we successfully mapped the response into an error, return that mapped error, otherwise return a RemoteServiceHttpError

It’s tempting to create a global error mapper which takes any kind of Response and maps it into one of the error types. But I think that would be a mistake. Each Retrofit client should map the errors unique to that client — that way OAuthClient doesn’t have knowledge of ApiClient's error types, and vice-versa.

For the OAuth client, the error mapper looks like this:

Displaying errors to the user

Due to the work we’ve done above, this is super easy. Now that we’re in the UI layer of the app, we can be a bit more liberal with what we handle in one place.

I created an extension method on Error called userDescription() which decides what to display to the user:

So, wrapping it all up, we can enqueue() a Call and display a friendly error message to the user like so:

We’re now confident that all possible failures will be delivered, without exposing any sensitive or technical error information.

You can find the custom CallAdapter in the gist below. Usage is something like:

You supply this to the call adapter, which is then supplied to the Retrofit builder:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store