A couple of days ago, in the midst of a conversion-to-Kotlin surge, I’ve stumbled upon an old bus implementation. As it always happens in these cases, my fingers moved automatically, and two seconds after my class was already in Kotlin.
I’ve stared at it though, unsatisfied. In that naive conversion, there was something missing. I was wondering, which Kotlin unique feature can I use to go where no Java developer has never been before?
After minutes of tinkering, finally, the illumination: sealed classes!
But let’s start from the beginning!
The Reactive Bus
A bus implementation comes really in handy when you want to decouple the execution from the place when an event happens, keep a bunch of diverse objects updated or implement a command pattern.
With RxJava its implementation is really straightforward.
Things are getting a bit awry if we want to post on this bus a large variety of different events: let’s say, multiple events from your user interface. In this case, a single type wouldn’t fit.
We can use a container class, let’s say
ReactiveEvent<T>, wrapped around yet another generic type
T and use this wrapper to fill the traffic on our bus. The downside of this approach is that we can potentially create a container of any type, and the more the project will scale, the more of different events we’ll need to handle, the more messy and untidy will be the subscriber code.
Using Sealed classes
I’ll avoid introducing once again the sealed classes: if you ended up reading this article, you might know more than me about this argument. They become really useful in combination with a bus implementation, lettin’ us keep our code sleek and well-ordered.
Let’s say our bus would contain only events generated from user actions, such pressing a
Button or start typing on a
We can think of a super-type
UserEvent that is extended by two classes representing two specific user actions:
Side note: the extended classes can also be data classes.
Next step would be communicating to our bus that we want to post and receive only instances of these two classes. This is easily achieved by implementing the bus with the sealed class super-type:
If we show the Kotlin’ compiler we’re a good sealed-class citizen, it will reward us with a lot of aids and warnings when we’re about to do something wrong. All of this even before it comes into our mind to hit run and compile the project.
Let’s imagine that while you’re coding, you find yourself suddenly drunk (it can totally happen, especially on Friday late afternoons) and you want to pass an instance of type
String into the bus. You don’t notice that this wouldn’t work (as you’re drunk), but it happens that the compiler does.
It will not only promptly warn us that among all the classes allowed to be posted in the bus, unfortunately,
String is not one of those, but it will also generate a compiler error, forcing us to deal with it.
Another advantage comes when we start writing the subscriber logic to handle these events, as we can make use of the never-too-esteemed
whenblock to easily filter them. This block is easily the best an Android developer can get in terms of code cleanness.
Note that for every branch the received instance will be smart-casted automagically to the checked type, allowing us to access them without additional code.
Cool huh? But it is even cooler considering that the compiler will suggest us this code even before we start writing it.
Or bothering us (with another error) if we are missing a branch.
Sealed classes is a wonderful Kotlin specific feature, especially when is combined with the checks the compiler is able to perform while we’rewriting. Using them, together with all the other features Kotlin has to offer, will let us not only write a more idiomatic code, but also a safer one.