Expecting the Exceptions
Most of the APIs have a defined format in which they return errors. This contract helps the API consumers in handling the errors.
At m.Paani, we too have a contract for raising errors. The API sends a JSON list containing N error objects. A typical error response would look something like
field: Contains name of the field only if it’s a field error else returns non_field_errors
code: Internal Error Codes (as most non trivial systems have a lot of domain specific errors and edge cases). This also provides granularity and makes debugging a bit easier.
Consider this - API returns an HTTP 401 UNAUTHORIZED, it’s impossible to identify the actual cause. This could have been raised because token was invalid or expired or perhaps it wasn’t sent in the first place.
client_message: A user friendly message that can be directly shown to the user.
server_message: A message containing more technical information that can help in debugging the issue.
Now that we know the format of error we’re dealing with, let’s dive into handling it at the Android side.
Here’s the data
class which would be used to deserialize the error body.
Here’s an example exception which would be raised once a corresponding JSON error is encountered.
This is how you would be handling errors in your API calls.
Let’s get to the interesting part, shall we?
handleErrors()
is an extension function written in Kotlin that can be used on any Single
.
It has a very straight forward responsibility
To transform the error response into a list of
NetworkError
and map every item to the correspondingException
and return aCompositeException
RxJava’s onErrorResumeNext
is the backbone of this extension function.
If you’ve never heard of this operator before, I would highly recommend reading a bit about it here, but just to summarize what it does
It replaces the current stream with a new Observable as soon as an error is encountered.
The function is pretty self explanatory but a higher level breakdown would be
- Hijack the thrown exception.
- Continue only if it’s an
HttpException
else just return a newSingle.error()
with the encountered exception to make sure the subscriber’sonError
is called. - Now if it is an
HttpException
, then convert the response body into aList<NetworkError>
using another extension function calledparseError()
. - Iterate over the list of errors and add their corresponding exceptions to the
errorList
. This basically means handling all of the BASE errors like Not Found Exceptions, Auth Exceptions, Empty Field Exceptions, etc. - Add all of the unknown errors into the
unhandledErrorList
and pass it to the lambda function received. Most of the APIs would have unique errors which cannot/should not be handled by this function. It should rather be delegated to the caller. - Based on the function contract, the lambda function would then process the
unhandledErrorList
and return aList<Throwable>
containing all of the API specific exceptions. - Add the new exceptions to the
errorList
and return a newSingle.error()
with theCompositeException
containing all of the modified exceptions. - The subscriber can then iterate over the
CompositeException
and act accordingly.
What do you think about this approach. Feel free to share your thoughts.
If you liked what you just read and think you would be able to help us in solving challenging problems to empower the next billion users in India, then make sure you reach out to us at dream@mpaani.com. We are always looking for smart folks passionate about bringing a change in the society.