When we introduced our API error handler we threw all possible non-success responses from the server in one bucket and handled them the same way.
But this might not be the best idea. For example, a server response with status code 404
indicates that the resource we're trying to access is not available (anymore). On the contrary, a server response of status code 500
indicates that the server itself ran into an error. These two errors should be handled differently.
In this tutorial, we'll show you how you can globally deal with a certain type of error, e.g., expired auth tokens from 401
responses or broken server responses with 5xx
as the status code.
Retrofit Series Overview
- Getting Started and Creating an Android Client
- Basics of API Description
- Creating a Sustainable Android Client
- URL Handling, Resolution and Parsing
- How to Change API Base Url at Runtime
- Multiple Server Environments (Develop, Staging, Production)
- Share OkHttp Client and Converters between Retrofit Instances
- Upgrade Guide from 1.9
- Beyond Android: Retrofit for Java Projects
- How to use OkHttp 3 with Retrofit 1
- Synchronous and Asynchronous Requests
- Send Objects in Request Body
- Add Custom Request Header
- Manage Request Headers in OkHttp Interceptor
- Dynamic Request Headers with @HeaderMap
- Multiple Query Parameters of Same Name
- Optional Query Parameters
- Send Data Form-Urlencoded
- Send Data Form-Urlencoded Using FieldMap
- How to Add Query Parameters to Every Request
- Add Multiple Query Parameter With QueryMap
- How to Use Dynamic Urls for Requests
- Constant, Default and Logic Values for POST and PUT Requests
- Cancel Requests
- Reuse and Analyze Requests
- Optional Path Parameters
- How to Send Plain Text Request Body
- Customize Network Timeouts
- How to Trust Unsafe SSL certificates (Self-signed, Expired)
- Dynamic Endpoint-Dependent Interceptor Actions
- How to Update Objects on the Server (PUT vs. PATCH)
- How to Delete Objects on the Server
- Introduction to (Multiple) Converters
- Adding & Customizing the Gson Converter
- Implementing Custom Converters
- How to Integrate XML Converter
- Access Mapped Objects and Raw Response Payload
- Supporting JSON and XML Responses Concurrently
- Handling of Empty Server Responses with Custom Converter
- Send JSON Requests and Receive XML Responses (or vice versa)
- Unwrapping Envelope Responses with Custom Converter
- Wrapping Requests in Envelope with Custom Converter
- Define a Custom Response Converter
- Simple Error Handling
- Error Handling for Synchronous Requests
- Catch Server Errors Globally with Response Interceptor
- How to Detect Network and Conversion Errors in onFailure
Distinguish Server Error Responses
For the next few minutes we'll assume your server is compliant with HTTP standards and sends appropriate status codes, e.g., 404
for resources not found. In the Retrofit's onResponse()
callback you've access to the response status code via the response.code()
method.
A simple solution would be to check this code in the error scenario and act accordingly:
call.enqueue(new Callback<List<GitHubRepo>>() {
@Override
public void onResponse(Call<List<GitHubRepo>> call, Response<List<GitHubRepo>> response) {
if (response.isSuccessful()) {
Toast.makeText(ErrorHandlingActivity.this, "server returned so many repositories: " + response.body().size(), Toast.LENGTH_SHORT).show();
// todo display the data instead of just a toast
}
else {
// error case
switch (response.code()) {
case 404:
Toast.makeText(ErrorHandlingActivity.this, "not found", Toast.LENGTH_SHORT).show();
break;
case 500:
Toast.makeText(ErrorHandlingActivity.this, "server broken", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(ErrorHandlingActivity.this, "unknown error", Toast.LENGTH_SHORT).show();
break;
}
}
}
@Override
public void onFailure(Call<List<GitHubRepo>> call, Throwable t) {
Toast.makeText(ErrorHandlingActivity.this, "network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
}
});
While this would work, it's quite inefficient. You would have to copy and paste this code into every single response callback. Especially when you want to change the behavior this quickly turns into a nightmare. Even if you move the logic into a central method, you would have to remember to call this method in every single response callback.
The best way to deal with global error scenarios is to handle them in one central place for all requests: an OkHttp interceptor.
Global Error Handler: OkHttp Interceptor
We've used OkHttp interceptors to act globally within the app in previous tutorials, for example to add query parameters to every request. The difference to this time is that we're intercepting the request on it's way back. Instead of modifying the request, we're intercepting the server response! Specifically, we take a look at the status code and, if it's a status code of 500
, we open a separate activity to inform the user that the servers are currently unavailable. The user won't be able to further interact with the app and run into even more undefined behavior.
The way we add a response interceptor is almost the same as adding the request interceptors we've used before:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
// todo deal with the issues the way you need to
if (response.code() == 500) {
startActivity(
new Intent(
ErrorHandlingActivity.this,
ServerIsBrokenActivity.class
)
);
return response;
}
return response;
}
})
.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://10.0.2.2:3000/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
As you can see in the snippet above, the okhttp3.Response response = chain.proceed(request);
line accesses the server response. Consequently, we can check the status code with if (response.code() == 500)
and then open the ServerIsBrokenActivity
.
Depending on your use case and the requirements you'll need to adjust this behavior and the possible status codes to your scenario. Also, just as a heads-up, the response will still arrive at the callback! Make sure you are not configuring conflicting app behavior.
The advantage of this approach is: every request made with the retrofit
object will deal with the error in the same way. You only have a single place for all major error handling. We recommend moving this into the ServiceGenerator as well. Fantastic!
Summary
In this tutorial you've learned how you can use a response interceptor to globally catch server errors. You can significantly increase the quality of your app when you deal with error scenarios the right way. Don't cut corners here!
Do you have further questions on this topic or about Retrofit in general? Just let us know on Twitter @futurestud_io or leave a comment below.
Enjoy coding & make it rock!