You are currently viewing Kotlin Sealed Classes: When to Use Them

Kotlin Sealed Classes: When to Use Them

Kotlin is a modern, statically-typed programming language that offers many features to help developers write concise and expressive code. One of these features is sealed classes, which are a powerful tool for representing restricted class hierarchies. Sealed classes allow you to define a fixed set of subclasses, making them ideal for modeling scenarios where a type can be one of a limited set of possibilities.

In this guide, we will explore the concept of sealed classes in Kotlin, understand their syntax and use cases, and examine their benefits. We will also look at practical examples to illustrate how sealed classes can be effectively used in real-world scenarios. By the end of this guide, you will have a solid understanding of when and how to use sealed classes in your Kotlin applications.

Defining Sealed Classes

Basic Syntax

A sealed class in Kotlin is declared using the sealed keyword. The subclasses of a sealed class must be defined within the same file.

sealed class Result

data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
data object Loading : Result()

In this example, Result is a sealed class with three possible subclasses: Success, Error, and Loading. This allows you to define a limited set of possible types for Result.

Properties and Initialization

Sealed classes can have properties and constructors just like regular classes.

sealed class Operation(val number: Int)

data class Addition(val value: Int) : Operation(value)
data class Subtraction(val value: Int) : Operation(value)

Here, Operation is a sealed class with a property number. The subclasses Addition and Subtraction inherit this property and initialize it through their constructors.

Use Cases for Sealed Classes

Representing State

Sealed classes are ideal for representing different states in an application, such as loading, success, and error states.

sealed class NetworkState

data object Loading : NetworkState()
data class Success(val data: String) : NetworkState()
data class Failure(val error: String) : NetworkState()

In this example, NetworkState is a sealed class representing different network states. This allows for a clear and concise representation of various states.

Handling Results

Sealed classes are useful for handling results that can be one of several types, such as the result of an API call.

sealed class ApiResponse

data class Success(val data: String) : ApiResponse()
data class Error(val message: String) : ApiResponse()
data object Empty : ApiResponse()

Here, ApiResponse can be either Success, Error, or Empty, providing a structured way to handle different outcomes.

Modeling Hierarchies

Sealed classes are effective for modeling class hierarchies with a restricted set of types.

sealed class Shape

data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
data class Square(val side: Double) : Shape()

In this example, Shape is a sealed class with three possible shapes: Circle, Rectangle, and Square.

Benefits of Sealed Classes

Exhaustive when Statements

One of the key benefits of sealed classes is the ability to use exhaustive when statements. The compiler ensures that all possible subclasses are handled.

sealed class Result

data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
data object Loading : Result()

fun handleResult(result: Result) {

    when (result) {
        is Success -> println("Success: ${result.data}")
        is Error -> println("Error: ${result.message}")
        Loading -> println("Loading...")
    }

}

fun main() {

    val result: Result = Success("Operation was successful")
    handleResult(result) // Output: Success: Operation was successful

}

Here, the when statement handles all possible subclasses of Result, ensuring that no case is missed.

Type Safety

Sealed classes provide type safety by restricting the possible types to a predefined set. This reduces the risk of runtime errors and makes the code more predictable.

sealed class Shape

data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
data class Square(val side: Double) : Shape()

fun handleShape(shape: Shape) {

    when (shape) {
        is Circle -> println("Circle with radius: ${shape.radius}")
        is Rectangle -> println("Rectangle with width: ${shape.width} and height: ${shape.height}")
        is Square -> println("Square with side: ${shape.side}")
    }

}

fun main() {
    val shape: Shape = Circle(2.5)
    handleShape(shape) // Output: Circle with radius: 2.5
}

In this example, the when statement handles all possible shapes, ensuring type safety.

Improved Readability

Sealed classes improve code readability by clearly defining a set of related types. This makes the code easier to understand and maintain.

sealed class AuthenticationState

data object Authenticated : AuthenticationState()
data object Unauthenticated : AuthenticationState()
data class AuthError(val error: String) : AuthenticationState()

Here, AuthenticationState clearly defines the possible states related to authentication, making the code more readable.

Practical Examples

Network Response Handling

Sealed classes are useful for handling network responses in a structured way.

sealed class ApiResponse

data class Success(val data: String) : ApiResponse()
data class Failure(val error: String) : ApiResponse()
data object Loading : ApiResponse()

fun handleApiResponse(response: ApiResponse) {

    when (response) {
        is Success -> println("Data: ${response.data}")
        is Failure -> println("Error: ${response.error}")
        Loading -> println("Loading...")
    }

}

fun main() {

    val response: ApiResponse = Success("Hello, World!")
    handleApiResponse(response) // Output: Data: Hello, World!

}

In this example, ApiResponse is a sealed class representing different outcomes of an API call. The handleApiResponse function uses a when statement to handle each possible outcome.

UI State Management

Sealed classes are effective for managing UI states in an application.

sealed class UiState

data object Loading : UiState()
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()

fun renderState(state: UiState) {

    when (state) {
        is Loading -> println("Loading...")
        is Success -> println("Data: ${state.data}")
        is Error -> println("Error: ${state.message}")
    }

}

fun main() {

    val currentState: UiState = Success("Data loaded successfully")
    renderState(currentState) // Output: Data: Data loaded successfully

}

In this example, UiState is a sealed class representing different UI states. The renderState function uses a when statement to render the appropriate UI for each state.

Conclusion

Sealed classes in Kotlin provide a powerful way to define restricted class hierarchies, making them ideal for representing a fixed set of related types. They offer several benefits, including exhaustive when statements, type safety, and improved readability. By understanding when and how to use sealed classes, you can write more expressive and maintainable Kotlin code. Practical use cases such as handling network responses and managing UI states demonstrate the effectiveness of sealed classes in real-world scenarios.

Additional Resources

To further your understanding of Kotlin sealed classes and their capabilities, consider exploring the following resources:

  1. Kotlin Documentation: The official documentation for Kotlin. Kotlin Documentation
  2. Kotlin by JetBrains: Learn Kotlin through official JetBrains resources. Kotlin by JetBrains
  3. Kotlin for Android Developers: A comprehensive guide to using Kotlin for Android development. Kotlin for Android Developers
  4. Kotlin Koans: Interactive exercises to learn Kotlin. Kotlin Koans
  5. KotlinConf Talks: Watch talks from the Kotlin conference. KotlinConf Talks

By leveraging these resources, you can deepen your knowledge of Kotlin and enhance your ability to develop efficient and maintainable applications.

Leave a Reply