1.) This article may be outdated
2.) This is a draft
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 custom
CallAdapter, 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:
I like to return custom
Error objects that expose more information about the error. For example, for an invalid HTTP response like, 400, 500, etc. I return a
RemoteServiceHttpError which contains the status code,
isServerError() function, and various other stuff. For a network failure, I return
In the project I’m working on, I have a Retrofit client called
OAuthClient for dealing with authentication, and another client called
ApiClient for calls to the backend.
OAuthClient can return an
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 the
CallAdapter accept an optional
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
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.
OAuth client, the error mapper looks like this:
Displaying errors to the user
Deciding what to display to the user when an error occurs is always challenging. Some API’s provide a user-readable message. Sometimes you want to provide the error code, and other times you want to hide technical details from the user. Whatever your approach, these decisions are part of the UI layer, and don’t belong in the same place where you consume your REST API.
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
userDescription() which decides what to display to the user:
So, wrapping it all up, we can
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: