Kotlin Sealed Class primer

When coming back to using Kotlin I re-remember the great features of this language that make it a joy to work with. In this post, I want to give a quick primer on the sealed classes feature.

It's not a new feature. Most experienced engineers working with Kotlin and/or Android probably already know about this. But people are joining the Kotlin/Android world with all kinds of experiences and backgrounds. So I think, it's worth revisiting some "old" features of this great language, even if it helps just a single less experienced engineer learn about something that will make their work more efficient :)

Without further ado, a sealed class is a class that allows having a very specific number of sub-classes.

Where is this useful? Almost every engineer who defined an enum at some point needed to have some additional data stored for (at least) some of the enum values. The ugliest solution to this problem comes in the form of variables that are only used when specific enum values are active with the comment:

// ONLY filled when "status == DISCONNECTED"
var reason = ""

Sealed classes give a very elegant solution to this very common use case (among others). Since a sealed class has a very specific number of subclasses, similar to the predefined values of an enum, we are able to group "enum" values + data, only when necessary.

sealed class Status { // 1.
    object Unknown : Status() // 2.
    class Disconnected(val reason: String? = null) : Status() // 3.
    object Connecting : Status()
    object Connected : Status()
}
  1. All subclasses inheriting the sealed classes must be present in the same Kotlin module. Note that by default the sealed class is abstract. You cannot initialize the class itself, only a sub-class.
  2. If a subclass has no additional data to hold, you can use the object keyword (instead of class) to create a singleton instead.
  3. Here the (disconnected) reason is grouped together with the disconnected status. The rest of the sub-classes can have their own related data (or none at all).

For business logic specific to sub-classes, use the powerful when statement with smart-casting:

when (status) {
    is Status.Unknown -> [...]
    is Status.Disconnected -> // Access `it.status.reason`
    is Status.Connected, is Status.Connecting -> [...]
}

Hopefully, this was useful and you can use it in your apps to make your coding life a bit more pleasant.

Happy coding!