Coroutines, Retrofit, and a nice way to handle Responses
Before we start, I assume the reader has knowledge and base concepts of Kotlin, Coroutines, ReactiveX, and Android App Architecture.
One-shot async calls
With coroutines becoming more popular theses days I’ve decided to update my ViewModel-Repository-DataSource communication logic and change it from ReactiveX to Coroutines. There’s nothing wrong with Rx, actually, Kotlin has data streams - checkout Flow and Channel -, but I completely agree with what Daniel Lew said in his Grokking Coroutines talk:
“…concurrency doesn’t have anything to do with streams of data.”
How many of us have implemented a scenario where “one-time fetch operation” is used to return data, complete, or throw an error:
What we really intend for is an asynchronous task that will fetch data, maybe perform some operations and finally notify us when ready. We don’t need a Single or Completable for that.
Bearing in mind that I have already successfully implemented this approach in a few projects, I decided to write about it.
Asynchronous architecture
First, let’s draw our architecture. We’ll have a BaseService responsible for handling network calls, successes and errors. ChildServices will extend BaseService. Our Repository will communicate with ChildServices and manage all the information before returning it back to the ViewModel with a Response mapped to ViewModel’s world.
Api and ChildServices are pretty straight forward:
note: ApiService it’s an abstraction layer to better isolate API logic from BaseService.
Now the fun part, Repository. In our BaseService we are returning a Result<T> with either a Success or an Error:
Thus, that’s what we’ll be returning in our Repository too:
And for the last piece of the puzzle, the ViewModel (child):
And that’s it!
You might have noticed that my repository calls are wrapped in a safeCall
function. A simple function that lives in a BaseViewModel:
Its purpose is to return data on successful calls or handle the error through parseError(e)
(which could show a Toast, Snackbar, etc…).
However, in some cases, we may need to return more detailed information to the ViewModel or provide a better error handling UX experience, for instance, an error animation depending on the type. To do so, let’s rewind a bit, and focus again in our Repository (that’s why I’ve called it the fun part).
“Now the fun part, Repository.”
is Result.Success -> result.data
is Result.Error -> throw result.exception
Let’s update this piece of code with a more elegant approach. For that, let’s think of it as a “UseCase” response. A sealed class
is a good candidate because it can represent states and hold data for a specific state:
Next we update our api call to:
Finally, our ViewModel:
This way the code is easier to read because it documents itself, and becomes easier to maintain and test.
Fun fact, Florina Muntenescu published an article Sealed with a class that talks about how to achieve this kind of behaviour. I was happy to notice that I was using her approach too.
Conclusion
With this refactor I’ve eliminated the need of an additional library, RxKotlin in this case, but most importantly - because one can argue that I could have used Flows and Channels too without importing RxKotlin -, we are using the right tools for the job:
An asynchronous programming pattern implemented with coroutines to perform “one-shot calls”.
Regarding Android App Architecture our Service and Repository communicate by a sealed class Result
. Then, before returning it to ViewModel, if needed, the Repository maps it again into another sealed class
representing a “UseCase”. This way a detailed response is returned and we also benefit from a cleaner code.
I hope you find this article useful, thanks for reading.
Level Up Coding
Thanks for being a part of our community! Subscribe to our YouTube channel or join the Skilled.dev coding interview course.