Concise Error Handling with LiveData and Retrofit

While working with Architecture components, I noticed the great ease of getting a response from my repository(API) whenever data is available, but this breath of fresh air also brought about a bit of pollution.
Now for which ever architecture design be it MVVM or MVP, we usually would want our view to interact with the repository through the ViewModel so as deliver latest updates if something(configuration change) where to happen to our activity. The problem I noticed was I was having to do almost the same number of checks every time I received a response from the server. These checks include:
- If
response.body()
is not null. - If no exception is thrown on the
onFailure()
method. - Having to do checks on my view if an exception was thrown so I handle it else I display my data.
Now if you come to think of it, these problems where already existing even before the conception of Architecture Components, but they just became more obvious I think. Also when I receive an update to my observer on the onChangeListener
I ended up having to do so many checks within that listener or before I set the value to my live data, which still kept a hole on how do I retrieve my errors so I can give the user feedback on the view.
So I thought of abstracting the whole process from the network requests callbacks to even my data models to use a wrapper then also modified my Observer
so it could return custom exceptions which I could use to notify the user of errors on their request.
Now note these are only done for long running network processes and probably this might not be the best approach for some other use cases.
API Concerns
So first of all I thought of simplifying my retrofit callback method by implementing the Callback<T>
interface and providing my own implementation which would handle the error checks and make sure it only delivered actual processed data. To do that we need to first write a ApiCallback.java
class which implements the retrofit Callback<T>
class.
So this class handles error cases, success and exceptions then delivers a result depending on what method is executed. So invariably this reduces some “if checks” we would always have to repeat. Next we apply this class to our retrofit call. Note to make the error handling seem less I created a special model which I would use with my LiveData instance, this model has only two properties which are a Generic data type and an Exception.
public class DataWrapper<T> {
private Exception apiException;
private T data;
}
For brevity I used the lombok plugin which auto generates a getter and setter kind of like Kotlin. This above method is used as a wrapper for all my objects that will be observed for a change regarding api calls and feedback.
Now that we have our DataWrapper
, let’s go on ahead and create a GenericRequestHandler
to take care of logic which will be commonly used for api calls.
After creating this class, all we need to do is extend the class provide an implementation of the makeRequest()
method, then call doRequest()
which would return a LiveData
of the Generic type passed into the class.
So now I extend my GenericRequestHandler
and override makeRequest()
to .return a retrofit Call
, then the AuthRequest()
method calls and returns a LiveData
object gotten from the inherited doRequest()
method.
Now we can see how abstracting our request logic gives us a cleaner code base, and keeps us from repeating the same things over again especially when it comes to error handling.
View Concerns
Now to make life easier for myself yet again I created a custom Observer which returns an exception
or a success
.
Now after defining our ApiObserver
we can go ahead and put the pieces together in our view.
Now it may seem like a lot of code to write at first but the benefits accrue over time where you don’t have to repeat your self with mindless if checks and exception/error handling both from the retrofit calls all the way to displaying an error in the view.
Error handling is an important process in app development especially when it comes to api calls, but when you keep repeating multiple checks it can get tiring and most times we end up leaving important checks out.
Also as a jumper starter you can checkout this library I wrote that helps with error handling and returning LiveData together with exception propagation to the view as described in this post.
Thanks for reading do well to leave your comments on any questions you might have.