Im currently working with my team on a new project. One part of it is a gateway service that distributes calls between microservices and/or merges results. We've been looking for a good solution to properly make requests using coroutines. Another requirement was to handle properly responses from the API and that we wont need to handle manually all common error results or IO exceptions.
After looking for some options we found out Retrofit to be the perfect fit for our purpose. But it wasn't easy to find a proper solution to handle successful and failed responses. Id like to give you a simple example of how to configure Retrofit using the NetworkResponseAdapter library to properly and gracefully handle responses in your code with a simple extension function.
Lets define a simple Retrofit client interface with one method to get a list of categories:
https://gist.github.com/marrek13/f35fdd24eb18ecee90826f92b76c53da
The problem with this definition is that when there is an exception we will expose the network exception directly to our application layer. It is not a great solution and its better to throw instead some contextual exception connected with what the client is doing. To achieve this goal we defined our own exception:
https://gist.github.com/marrek13/e1405c25fe6ee70aa1afa380cbd70004
We added one line to the Retrofit builder
.addCallAdapterFactory(NetworkResponseAdapterFactory())
Then the client was modified to use NetworkResponseAdapter:
https://gist.github.com/marrek13/fc3cc307bb8441c7e575c789e0b744c7
NetworkResponse is a sealed class whose result can be one of the classes below:
NetworkResponse.Success : an object containing the successful result with response data for any 2XX response
NetworkResponse.ServerError : an object which contains the response from the call that resulted in a non-2XX status
NetworkResponse.NetworkError : the result of a request that didn't result in a response
NetworkResponse.UnknownError : the result of a request that resulted in an error different from an IO or Server error
NetworkResponse.Error : object for any other possible response not covered by any of previous cases
As you noticed we need to provide the type arguments for deserialization: first for the Success response and the second for the ServerError response.
To achieve the goal which is easy handling of all exceptions/errors in the same manner we introduced an extension function to the NetworkResponse class:
https://gist.github.com/marrek13/467be212bd60c38b8883f65cca58b0b9
Having all of this together, we can easily call our API in the service without being worried about leaking the IOExceptions from the Retrofit client:
https://gist.github.com/marrek13/506fd4ed9d881a9ce9aab15db7a6f83f
And thats it. Adding the call() to all places where the client is used is the only downside of this solution. But in general, we consider it as the most elegant and easy-to-read and test solution.
Top comments (0)