Cleaner Code With RxJava, Coroutines Kotlin Extensions, and Helper Functions
Kotlinizing RxJava and Coroutines for Android development
In this article, I will be explaining some of the Kotlin extensions and helper functions that I wrote for RxJava and Coroutines while introducing reactive patterns to my Android applications. I will be assuming that you have some familiarity with those libraries.
For each extension, I will describe the use case, how it can be implemented without an extension, how I’d prefer it to be used, and the extension that will implement that preferred way of using it. So without further ado, let us begin.
Scenario 1. Room With RxJava
Let’s say you have a Room query that returns a Flowable
; this is a typical use case that enables us to observe the database and act accordingly whenever an update occurs. What happens when we want to invoke that Room query only once without observing it? We can of course always create another query with a different return type, and that’s definitely the way to go in some cases, but if we still want to take advantage of RxJava in that case, we can write a simple extension for that.
The extension ideally would return a single invocation result on success and would allow for failure handling. The signature of that extension would look like this:
If we take a look at the Flowable
class, you’ll notice there is a function called blockingFirst()
which returns the first item emitted. It will throw a NoSuchElementException
if it emits no items and will throw a RuntimeException
if the source signals an error.
We can utilize this information to write the following extension function:
I separated both types of exceptions just to highlight the fact that you can catch the NoSuchElementException
specifically. To use that extension let’s assume you have a database query that looks like this:
To invoke this query once without having to observe it, you can use the extension function like this:
It’s worth mentioning that RxJava in general has an inherent ugliness in its nested nature; as you can imagine, with more complex logic the nesting might get out of hand. This issue can be solved with Coroutines; but for now, this extension will make it much cleaner than its straightforward implementation equivalent.
Scenario 2. Retrofit With RxJava
If you ever used RxJava for Retrofit network calls, then you’d know that RxJava handles HTTP errors (such as a 404) differently from other network errors (such as no internet connection), and that adds a lot of boilerplate code to every network call you make.
Ideally we’d be able to have an extension that accepts a success handler, a failure handler, and a completion handler, and that’s all we’d care about. The extension’s signature would look like this:
Notice that the Observable holds a Retrofit Response
object, which would be what we wrap our network response objects with; this way we can identify HTTP errors. We’d like the onFailure
to be called when any failure happens (HTTP or network), the onComplete
to be called after the network call, whether it succeeds or not, and the onSuccess
to be called if the network call is successful.
With that information in hand, here’s what the extension would look like:
Notice that we have to invoke onComplete
in two places; that’s because doOnComplete
will not be called if a network error occurs. If we get an HTTP error, then we can wrap the response inside an HttpException
and use that in the onFailure
handler.
On top of the onFailure
handler that we’re passing as a parameter, we can also have a universal error handler, and it would look something like this:
To use this extension, let’s assume you have a getTasks
GET request such as this one:
Now all you have to do to invoke this GET request is to write this code:
Note that you can choose not to pass an onFailure
and an onComplete
if you choose not to care about those for a specific use case.
Scenario 3. Retrofit With Coroutines
If you want to do the same thing that we did in Scenario 2 but with Coroutines, we can write a quick helper function instead of an extension. The function’s signature can look like this:
With the use of suspend
functions, using Coroutines will be much cleaner. As you can see, we don’t need to pass an onSuccess
handler because we can identify a success by returning a value; otherwise we can return null
. We also don’t need an onComplete
handler because anything we do after the call will be after completion.
The implementation of this helper function can look like this:
With this type of implementation you can easily add a universal error handler on top of the optional error handler you pass in this method; maybe you’d want to add some universal analytics or maybe you want to show a Snackbar for all network errors across the entire application. With that addition, it’ll look more like this:
To use this helper function, let’s assume you have a getTasks
Retrofit function that looks like this:
Then you can use it inside a CoroutineScope
, let’s say in the ViewModel
, like this:
As a final thought, I do realize that RxJava can still be useful, but I find that using Coroutines is much cleaner here and also doesn’t suffer from the nesting effect that RxJava has.
That’s it for this article. I hope you got some use out of it and that it helped you write cleaner code. Until next time, Dev Bites signing out!