Working with Kotlin Sealed Classes

undefined

Most of us have used the Enum types in our projects. But, what if we want to define different types of subclasses of the same parent type? Can we still go with an Enum class? Can we use a Sealed class? Does it have any benefit compared to the Enum class? What is a Sealed class after all? Let’s find it out in this article.

Welcome to our MindOrks write up on Sealed classes in Kotlin.

We have a video tutorial on this concept on our MindOrks youtube channel. You can check it out here.

Let’s understand the concept of Sealed classes by understanding its difference from the Enum classes with a use case. Let’s take a Result and let’s say there are only two types of Results: Success and Error.

enum class Result(val data: String) {
    SUCCESS("Success"),
    ERROR("Error")
}

So, Enum classes are helpful, but they have their limitations. For example, each subtype of the Enum class can be only a constant and it has no state. That is, what if instead of just one hardcoded "Error", there are different dynamic error messages(the states of the objects), it is not possible using Enum. Also, we cannot have different types of subclasses involved, which we will be seeing with Sealed classes in a while. Now, let's talk about Why the Sealed class over the Enum in detail with the example.

Why Sealed Class over Enum?

Sealed classes give us the flexibility of having different types of subclasses and also containing the state. The important point to be noted here is the subclasses that are extending the Sealed classes should be either nested classes of the Sealed class or should be declared in the same file as that of the Sealed class. We will see it later in this blog.

  • Enum types are constants and hence, it is difficult to maintain different states of the instances.
  • For example, in the Enum class Result that is considered above, we are storing only the string values attached to the Enum. But what if in the Error case, we want to display the actual Exception that has caused the error. It is not possible with enums since Enum types cannot hold the state of the type.
enum class Result(val message: String) {
    SUCCESS("Success"),
    ERROR(val exception: Exception) // not possible with enums
}
  • Subclasses of a sealed class, as discussed, are either ordinary classes, data classes or sealed classes themselves and hence it is easy to contain the state of the subclass.

Let’s consider an example: Firstly, let’s define a Sealed class Employee. How do we do this? By adding the keyword “sealed” in front of the class

sealed class Employee

Now according to our use case, we need to have the two types of Employees:

  • Manager(Name, age, list of reporting employees)
  • SeniorDev(Name, age, projects)

So let’s create two data classes: Manager and SeniorDev with the required constructor parameters:

sealed class Employee
data class Manager(val name: String, val age: Int, val team: List<String>): Employee()
class SeniorDev(val name: String, val age: Int, val projects: Int): Employee()

This is how we can have the state(name, age, and etc) in the sealed class which is not possible with enum. Now, let's talk about the advantages of Sealed Class over the Abstract Class.

Why Sealed Class over Abstract Class?

Sealed classes are abstract by itself, and cannot be instantiated directly. So let’s take a pause here. If Sealed classes are abstract by default, why can’t we use an abstract class instead of a sealed class in the first place? Well, the catch here is an abstract class can have their hierarchies anywhere in the project, whereas a sealed class should contain all the hierarchies in the same file.

To elaborate on this, if we try to define the SeniorDev class extending the sealed Employee class in a different file, we will be getting a compile-time error. That is how a Sealed class provides a restricted number of hierarchies, thereby providing the flexibility of defining different child classes of the parent sealed class. Here, the child classes can be data classes, ordinary classes, objects (Singleton in Kotlin), or sealed classes themselves and hence, the state of the Sealed class instance can be accessed as per requirement, similar to how a normal class object can be accessed.

sealed class Employee
data class Manager(val name: String, val age: Int, val team: List<String>): Employee()
class SeniorDev(val name: String, val age: Int, val projects: Int): Employee()
object JuniorDev: Employee()

Another important advantage of using a sealed class over abstract class is that it helps the IDE to understand the different types involved and thereby helps the user in auto-filling and avoiding spell mistakes. For example:

val employee: Employee  = SeniorDev("Name",20, 10)
val message = when (employee) {
    is Manager -> {
        "Welcome ${employee.name}! You have ${employee.team.size} employees in your team!"
    }
    is SeniorDev -> {"Welcome ${employee.name}! You have already ${employee.projects} projects under your belt!"}
    //is is not required for SingleTon
    JuniorDev -> {"Welcome aboard! We wish you an awesome Experience!"}
    //no else case is required since all cases are handled
}

We can take advantage of the auto-fill feature in this case. Usually when we use “when”, we have an option to auto-fill the different cases that can exist and since the IDE is aware of the different types of subclasses, there is no requirement to add an else case.

When we consider abstract classes, they allow defining hierarchies from various files in the project, and hence, the IDE cannot understand the different subclasses involved. Hence, the compiler throws an error stating that an else case should be mentioned. This is another benefit of using the Sealed class over the Abstract class.

abstract class Employee

data class Manager(val name: String, val age: Int, val team: List<String>): Employee()
class SeniorDev(val name: String, val age: Int, val projects: Int): Employee()
object JuniorDev: Employee()
Abstract Class When expression

We can see from the above image that there is a compiler error stating to add a necessary else branch since the parent Employee class was an abstract class and hence the IDE has no idea if all the hierarchies are covered or not. This is the limited hierarchy advantage of using a Sealed Class.

We can also apply the use of Sealed classes in different examples in the Android Project. Suppose you want to display different types of Result:

sealed class Result {
     data class Success(val data: Data) : Result()
     data class Error(val exception: Exception) : Result()
}
//Consider Data as a model class which holds the result

Here, we have a data class success object which contains the success Data and a data class Error object which contains the state of the error.

You can check out our video tutorial on this concept here

This way, Sealed classes help us to write clean and concise code! That’s all the information about Sealed classes and their usage in Kotlin.

Thank you for your time.