Every project has a time when you would like to pass some data. Frequently, this passed data needs to be different. This is problematic because most languages don’t support it in a clean and readable way.
Ideally, the access should be type-safe, or the complexity of the entire feature will skyrocket. This is where sealed class
comes to the rescue
1. What is a sealed class?
sealed class
is a class
that limits its inheritance to a package name. In short, you could visualize it like this:
![](https://www.droidcon.com/wp-content/uploads/2024/07/1_A2dxaRB_7QYp-vmYs8OSAw-600x185.webp)
From the image above, you can see that InheritExample2
and InheritExample3
can inherit from Example
while SomeClass
not!
In general, inheritance is not something we want because it increases complexity and limits the extensibility of a system. However, the system can be simplified if limited to a single package and used to hold data.
Moreover, if you pair sealed class
with when
in Kotlin, you’ll get a Typesafe way to access different fields and functions defined in inheriting classes (more about that below).
2. How to use it?
It’s straightforward to create a sealed class. Take a look at the following example:
sealed class Example {
data class ExampleWithName(val name: String) : Example()
data object EmptyExample : Example()
}
fun main() {
val exampleWithName = Example.ExampleWithName(name = "Name")
val emptyExample = Example.EmptyExample
}
If you don’t want to use the parent as the accessor, here Example.InheritingClassName
, you can write the classes outside of sealed class
block like this:
sealed class Example
data class ExampleWithName(val name: String) : Example()
data object EmptyExample : Example()
fun main() {
val exampleWithName = ExampleWithName(name = "Name")
val emptyExample = EmptyExample
}
It’ll work as long as Example
, ExampleWithName
and EmptyExample
are in the same package. However, I recommend using the first version if possible, as we know all the types and what we deal with!
The best practise is to keep them in a single file unless you’re using larger and more complex inheriting classes then splitting them into multiple files in the same package is the way to go
Additionally, note that the Example
class itself cannot be created. Only inheriting classes can be instantiated:
fun main() {
// Compilation error:
// Cannot access '<init>': it is protected in 'Example'
// Sealed types cannot be instantiated
val example = Example()
}
3. Type safe access with when
Kotlin compiler is smart enough to figure out the used type of class at compilation time and when
keyword makes our work so much easier!
Just take a look at a function that will change what prints depending on the passed Example
class:
sealed class Example {
data class ExampleWithName(val name: String) : Example()
data object EmptyExample : Example()
}
fun main() {
val exampleWithName = Example.ExampleWithName(name = "Name")
val emptyExample = Example.EmptyExample
printExample(exampleWithName) // Example with name: Name
printExample(emptyExample) // Empty example
}
fun printExample(example: Example) {
when (example) {
Example.EmptyExample ->
println("Empty example")
is Example.ExampleWithName ->
println("Example with name: ${example.name}")
}
}
Job Offers
Additionally, when
keyword throws a compilation error if all possible variants aren’t handled. If you need a default handler, use else
:
fun printExample(example: Example) {
when (example) {
Example.EmptyExample ->
println("Empty example")
else -> println("Default behavior")
}
}
Since you’re probably using Android Studio or IntelliJ Idea, there’s a helpful shortcut for writing these expressions. Type when(example)
and press (alt + enter):
![](https://www.droidcon.com/wp-content/uploads/2024/07/1_Vrw2GzNZQVLyxjXMj9SdtA-600x157.webp)
It’ll generate the following code:
fun printExample(example: Example) {
when (example) {
Example.EmptyExample -> TODO()
is Example.ExampleWithName -> TODO()
}
}
4. Real project usage:
Because of the flexibility of sealed class
they’ve found many usages in generic data holders. One of those is a response from the API. The data we get might be an Error
or Ok
result, meaning something went well or wrong.
Later on, we’re able to safely retrieve the data and change behavior depending on what we got:
sealed class ApiResponse<out T> {
data class Ok<out T>(val data: T) : ApiResponse<T>()
data class Error(val exception: Throwable) : ApiResponse<Nothing>()
}
// Usage
fun <T> handleResponse(response: ApiResponse<T>) {
when (response) {
is ApiResponse.Error -> // Handle error
is ApiResponse.Ok -> // Request successfull
}
}
Thanks for reading! If you’ve learned something new, please follow me for more information and clap! It means a lot!
Based on:
https://kotlinlang.org/docs/sealed-classes.html?source=post_page—–b657dc4bd6e9——————————–
This article is previously published on proandroiddev.com