Exploring Sealed Class vs Enum in Kotlin Which is Best for Your Code?

sealed class vs enum

What is a sealed class?

Sealed classes are a bit like having a magical box that can hold different kinds of things, not just crayons. Inside this box, you can have crayons of different colors, but you can also have stickers and even small toys. Each thing in the box might look different and do different stuff. So, sealed classes let you have a special box where you can keep crayons and more, and each thing inside can be special and have its own abilities.

Here’s why sealed classes are awesome:

  • Comprehensive Handling: When you’re using a sealed class in a when expression (a Kotlin construct for handling different cases), you’ll get a compile-time warning if you forget to handle any of its subclasses. This prevents bugs from sneaking in.
  • Readability Boost: Your code becomes more self-documenting. When you encounter a sealed class instance, you instantly know what potential states or types it could represent.

What are the advantages of sealed classes in Kotlin?

sealed class vs enum\


  1. Limited Subtypes: Sealed classes allow you to define a limited and controlled set of subclasses. This restricts the possible hierarchy and prevents unintended extensions, ensuring that all subclasses are known and accounted for.
  2. Exhaustive when Expressions: Sealed classes work seamlessly with Kotlin’s when expressions. Since all possible subclasses are known, the compiler can help enforce exhaustive pattern matching, reducing the likelihood of runtime errors.
  3. Maintainability: Sealed classes promote better organization and maintainability by grouping related subclasses together. This makes it easier to understand the hierarchy and manage changes over time.
  4. Readability: The use of sealed classes makes your code more self-documenting. When you see a when expression on a sealed class, you immediately understand the potential cases it covers, improving code readability.
  5. Compile-Time Safety: The compiler can identify cases where exhaustive pattern matching isn’t achieved, which helps catch potential bugs at compile time rather than runtime.
  6. Preventing Unintended Subclassing: Sealed classes prevent other classes from inheriting from them outside of the file where they’re defined. This guards against unintentional subclassing and ensures that the class hierarchy remains controlled.
  7. Encapsulation: Sealed classes can have abstract properties and methods, enforcing a common interface for all their subclasses. This helps maintain consistency and ensures that certain properties or behaviors are present in all subclasses.
  8. Code Navigation: When using sealed classes, IDEs can provide better navigation and code analysis features, since they know all the possible subclasses.
  9. Refactoring Support: When you need to add a new case to a sealed class, the compiler can help guide you to update all the necessary parts of your codebase that handle that class, thanks to the exhaustive pattern matching.
  10. Predictable Behavior: Since sealed classes control the subclasses that can be created, you can have more confidence in the behavior and interactions of these classes, reducing the risk of unexpected outcomes.
  11. Enhanced Collaboration: Sealed classes provide a clear structure that aids communication among team members. It’s easier to discuss and understand the class hierarchy when it’s limited and well-defined.

What is an example of a sealed class?

I’ll be happy to provide you with a coding example to illustrate the usage of sealed classes.

1. Example For Sealed Class

Suppose we’re creating a simple app to calculate the area of different shapes. We can use a sealed class called Shape to represent various shapes and their areas. Here’s how:

sealed class Shape {
    abstract fun calculateArea(): Double
}

class Circle(val radius: Double) : Shape() {
    override fun calculateArea(): Double {
        return Math.PI * radius * radius
    }
}

class Rectangle(val width: Double, val height: Double) : Shape() {
    override fun calculateArea(): Double {
        return width * height
    }
}

fun main() {
    val circle = Circle(5.0)
    val rectangle = Rectangle(3.0, 4.0)

    val shapes: List<Shape> = listOf(circle, rectangle)

    for (shape in shapes) {
        println("Area: ${shape.calculateArea()}")
    }
}
Kotlin

In this example:

  • We define a sealed class Shape with an abstract function calculateArea() which each subclass must implement.
  • We create two subclasses of Shape: Circle and Rectangle, each with its own implementation of calculateArea().
  • In the main() function, we create instances of Circle and Rectangle and store them in a list of type Shape.
  • We loop through the list and calculate and print the area of each shape using their respective calculateArea() implementations.

The sealed class restricts the subclasses to only those defined within the same file. It ensures a controlled set of subclasses and prevents external extensions.

2. Example For Sealed Class

Where you’re building a task management app using Jetpack Compose, Hilt for dependency injection, and ViewModel to manage the UI state. You want to display tasks with different priorities: High, Medium, and Low.

// TaskPriority sealed class
sealed class TaskPriority {
    object High : TaskPriority()
    object Medium : TaskPriority()
    object Low : TaskPriority()
}

// Task data class
data class Task(val id: Int, val title: String, val priority: TaskPriority)

// TaskViewModel
@HiltViewModel
class TaskViewModel @Inject constructor(private val taskRepository: TaskRepository) : ViewModel() {
    val tasks: State<List<Task>> = taskRepository.getTasks().stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}

// TaskListScreen composable
@Composable
fun TaskListScreen(viewModel: TaskViewModel = hiltViewModel()) {
    val tasks by viewModel.tasks.collectAsState()

    LazyColumn {
        items(tasks) { task ->
            TaskItem(task)
        }
    }
}

@Composable
fun TaskItem(task: Task) {
    val taskPriority = task.priority

    when (taskPriority) {
        is TaskPriority.High -> {
            TaskCard(task, color = Color.Red)
        }
        is TaskPriority.Medium -> {
            TaskCard(task, color = Color.Yellow)
        }
        is TaskPriority.Low -> {
            TaskCard(task, color = Color.Green)
        }
    }
}

@Composable
fun TaskCard(task: Task, color: Color) {
    Card(
        modifier = Modifier.padding(16.dp),
        backgroundColor = color,
        elevation = 4.dp
    ) {
        Text(
            text = task.title,
            modifier = Modifier.padding(16.dp),
            fontWeight = FontWeight.Bold
        )
    }
}
Kotlin

In this example:

  • The TaskPriority sealed class represents different task priorities: High, Medium, and Low.
  • The Task data class represents a task with an ID, title, and associated TaskPriority.
  • The TaskViewModel uses Hilt for dependency injection and manages the list of tasks. It fetches tasks from a repository and exposes them as a State using Kotlin’s Flow.
  • The TaskListScreen composable collects the tasks from the ViewModel using collectAsState and displays them in a LazyColumn. The TaskItem composable uses the priority to decide on the color of the TaskCard.

This example showcases how you can combine sealed classes with Jetpack Compose, Hilt, and ViewModel to build a task management app that displays tasks with different priorities, each with its own UI representation.

While enums are great for simple sets of values, sealed classes offer a more flexible and adaptable solution when your data needs are more complex and may evolve over time. In this example, using sealed classes for task priorities allows for additional data, behavior, and extensibility that enums wouldn’t provide.

What are the benefits of using a sealed class over enum?

Using a sealed class over an enum offers several benefits, especially when dealing with more complex scenarios:

  1. Hierarchy and Behavior: Sealed classes allow you to define a hierarchy of classes with behavior. Each subclass can have its own properties and methods, providing more flexibility compared to enums where each value is a fixed instance.
  2. Extensibility: Sealed classes are open for extension. You can create new subclasses within the same file, which is not possible with enums. This makes sealed classes a better choice when you anticipate adding more variations in the future.
  3. Pattern Matching: Sealed classes work well with pattern matching (or when statements). You can perform more advanced and detailed matching based on the type and properties of the instances, which is not as flexible with enums.
  4. Code Organization: Sealed classes allow you to separate the implementation details of different variations into individual classes, leading to more organized and maintainable code.
  5. Code Reusability: Sealed classes can have shared properties and methods across their subclasses, promoting code reusability. Enums are limited in this regard as each enum value doesn’t allow for custom properties or methods.
  6. Type Safety: Sealed classes provide a higher level of type safety, as the compiler can enforce exhaustive checks when pattern matching. This helps catch potential issues during development.
  7. Complex State: If your variations have more complex internal state or need to interact with other parts of the codebase differently, sealed classes are a better fit. Enums are usually simpler and less versatile in handling complex state.
  8. Separation of Concerns: Sealed classes enable you to maintain separation between different behaviors, making it easier to extend or modify specific parts of the code without affecting the whole enum.
  9. Testing: Sealed classes can be more unit-testable since you can create mock instances of subclasses for testing specific scenarios.

While enums are great for simple and fixed sets of values, sealed classes offer more flexibility, extensibility, and behavior, making them a superior choice for cases where you need a hierarchy of variations with varying properties and methods.


Here are a few complex scenarios where using a sealed class could be more advantageous than an enum:

  1. User Authentication States: In an authentication system, you might have states like “Authenticated,” “Unauthenticated,” and “Locked.” These states can have varying properties and behaviors that a sealed class can handle more effectively.
  2. Network Responses: Handling different types of network responses such as “Success,” “Error,” and “Loading” can involve complex behaviors, error details, and loading animations. Sealed classes can encapsulate these variations better.
  3. Expression Evaluation: When building a mathematical expression evaluator, you might encounter various node types like “Number,” “Operator,” and “Function.” Each type could have its own properties and evaluation logic.
  4. UI Components: Designing UI components like “Button,” “TextField,” and “Dropdown” might require handling different interaction states, styles, and validation behaviors based on the context.
  5. Product Variants: Managing product variants in an e-commerce system involves handling diverse attributes like size, color, and material. A sealed class can encapsulate these attributes and their interactions.
  6. Document Generation: Generating documents might involve different document types like “Invoice,” “Report,” and “Receipt.” Each type could have its own structure, data, and formatting requirements.
  7. Permission Levels: In an access control system, you might have permission levels like “User,” “Moderator,” and “Admin.” These levels can have varying levels of access to different parts of the system.
  8. Messaging System: Implementing a messaging system could include message types like “Text,” “Image,” and “Video.” Each type might have different rendering and interaction behaviors.

What is an enum class?

Think of enums as a special type of box where you put crayons of different colors. Each crayon can only be one of the colors you’ve decided ahead of time, like red, green, or blue. You can use these colors to draw colorful pictures. But with enums, the crayons can only be those specific colors and nothing else. They’re simple and just represent one color each.

Why use enum instead of class?

Enums are like your trusty tool for handling situations where you have a fixed set of unchanging values, such as days of the week or error codes. They make your code easy to read because you can use meaningful names instead of obscure numbers. Plus, when you need to switch things up based on these values, enums play nicely with switch statements, helping you keep your code organized and neat. They’re a great fit for representing simple states too, like indicating whether something is “On” or “Off,” without the need for heavyweight classes. And the best part? Enums are efficient memory-wise since they’re usually stored as integers. They also add a layer of safety by preventing you from using incorrect values at compile time, and they’re a breeze to save and share since they’re easily serializable. So, when you’re dealing with straightforward scenarios and just need a clear and concise way to represent constants, enums are the way to go, keeping your codebase simple and effective.

What is an example of enum class?

enum class DayOfWeek {
    SUNDAY,
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY
}

fun main() {
    val today = DayOfWeek.WEDNESDAY

    when (today) {
        DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> println("It's the weekend!")
        DayOfWeek.FRIDAY -> println("TGIF!")
        else -> println("It's a regular day.")
    }
}
Kotlin

In this example, we’ve defined an enum class called DayOfWeek representing the days of the week. Each day is a constant value within the enum. We then use the when statement to perform different actions based on the day of the week. Enums make the code more readable and help prevent errors when working with days.

What is the purpose of a enum class?

The purpose of an enum class is to provide a way to define a set of named constant values within a single type. Enums are used to represent values that are mutually exclusive and won’t change frequently. They enhance code readability by giving meaningful names to these values, making it easier to understand the purpose and context of each value in your codebase.

Enum classes are particularly useful in scenarios where you need to represent a fixed set of options or states. They help prevent errors by restricting the possible values to a predefined list, ensuring that only valid choices are used. Enums are often employed in switch statements to handle different actions based on the specific enum value, promoting structured and organized code.

The purpose of an enum class is to offer a clear, concise, and type-safe way to represent constant values, making code more readable, maintainable, and less prone to errors.

Sealed Class vs Enum in Kotlin Which is Best for Your Code?

Choosing between a sealed class and an enum in Kotlin depends on the nature of your code and the specific requirements of your application. Both sealed classes and enums have their strengths and are suited for different scenarios. Here’s a comparison to help you decide which is best for your code:

Sealed Class:

  • Use When: Sealed classes are ideal when you need a closed hierarchy of related classes with shared behavior and properties. They are suitable for scenarios where you expect to add new subclasses in the future and want to encapsulate behavior within each subclass.
  • Benefits: Allows more flexibility and extensibility compared to enums. Each subclass can have its own properties and methods. Supports pattern matching for complex behavior differentiation.
  • Scenarios: Use sealed classes for representing complex states, variations with unique behavior, or when you want to encapsulate behavior within subclasses.
  • Example: Representing different shapes like circles, rectangles, and triangles with unique area calculation methods.

Enum:

  • Use When: Enums are a good choice when you have a fixed set of constant values that won’t change frequently. They are great for representing options, states, or status codes without complex behavior.
  • Benefits: Provides a clear and concise way to define and use a set of constant values. Enhances code readability and prevents errors caused by using incorrect values.
  • Scenarios: Use enums for representing simple, mutually exclusive values like days of the week, HTTP status codes, or basic states.
  • Example: Representing different colors like red, green, and blue as options.

What is the difference between sealed class and enum class?

Sealed Class:

  1. Purpose: A sealed class is used to define a closed hierarchy of classes that are related by a common theme. It’s often used to represent variations with distinct behavior and properties.
  2. Inheritance: Sealed classes allow inheritance. You can create subclasses that extend a sealed class, but all subclasses must be defined within the same file as the sealed class.
  3. Flexibility: Each subclass can have its own properties and methods, making sealed classes more flexible than enums in terms of encapsulating behavior.
  4. Pattern Matching: Sealed classes work well with pattern matching (when statements) to handle different behaviors based on the type of the instance.
  5. Use Cases: Use sealed classes when you have a set of related classes with shared behavior, but you also need individual behavior for each subclass. For example, representing different states or variations with distinct properties.

Enum Class:

  1. Purpose: An enum class is used to define a set of constant values that are mutually exclusive. It’s best suited for representing options, states, or simple values that won’t change frequently.
  2. Values: Enum classes define a fixed set of values at the time of creation and cannot be extended after compilation.
  3. Behavior: Enums can’t have custom properties or methods for each value. All enum values share the same behavior defined within the enum class.
  4. Switch Statements: Enums work well with switch statements, providing an organized way to handle different actions based on enum values.
  5. Use Cases: Use enum classes for representing straightforward options, states, or constants that are unlikely to change or require unique behaviors.

A sealed class is used for creating a hierarchy of classes with varying behavior, while an enum class is used for representing a set of constant, mutually exclusive values with consistent behavior. Your choice between them depends on whether you need flexibility and unique behavior (sealed class) or simplicity and fixed values (enum class) for your specific use case.

Feel free to follow and join my email list at no cost. Don’t miss out — sign up now!

Please check out my earlier blog post for more insights. Happy reading!

The Innovative Fundamentals of Android App Development in 2023

How to learn Android App Development in 2023 – Full Guide For Beginners using Kotlin Language

Ultimate Guideline on How to Use ViewModel Design Pattern

What is the Repository Pattern in Android? A Comprehensive Guide

What is ViewModel in Android? A Good Guide for Beginners

The Innovative Fundamentals of Android App Development in 2023

How to Say Goodbye to Activity Lifecycle and Say Hello to Compose Lifecycle ?

How to Monitor/Observe Network Availability for Android Projects

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x