Kotlin Tutoial

Basics

Control Flow

Array & String

Functions

Collections

OOPs Concept

Exception Handling

Null Safety

Regex & Ranges

Java Interoperability

Miscellaneous

Android

Kotlin Sealed Classes

Sealed classes in Kotlin are used to represent restricted class hierarchies, wherein a value can have one of the types from a limited set. They're a way to ensure type-safety when evaluating cases for a specific type. Sealed classes are often used in scenarios where you have a fixed set of related types, like when modeling states, events, or commands.

Basics

  1. Declaration: A sealed class is declared using the sealed modifier.
  2. Subclassing: All direct subclasses of a sealed class must be declared within the same Kotlin file as the sealed class itself.

1. Simple Example

Consider modeling a UI state:

sealed class UiState {
    object Loading : UiState()
    data class Success(val data: String) : UiState()
    data class Error(val error: Throwable) : UiState()
}

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

Benefits:

  1. Exhaustiveness: The Kotlin compiler checks if every possible type in the sealed class hierarchy is covered in a when expression. This means you won't miss any cases accidentally.
  2. Encapsulation: Sealed classes encapsulate a fixed set of types, ensuring that you can't have unexpected types.

2. Nested Sealed Class Hierarchies

Sealed classes can also have subclasses which are sealed:

sealed class Expr {
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    sealed class Function : Expr() {
        data class Sin(val angle: Expr) : Function()
        data class Cos(val angle: Expr) : Function()
    }
}

3. Sealed Interfaces

Starting from Kotlin 1.5, you can also have sealed interfaces:

sealed interface Operation {
    object Add : Operation
    object Subtract : Operation
    data class Multiply(val factor: Int) : Operation
}

Tips:

  1. Data Classes: Sealed class hierarchies often contain data classes to hold specific pieces of data for certain types.
  2. Object Declarations: If a type doesn't need to hold any data, it can simply be an object declaration. This makes it a singleton, and you can't create multiple instances of it.

Summary:

Sealed classes in Kotlin provide a way to represent a fixed set of related types. They enhance type-safety, enable more readable and maintainable code, and work great with when expressions by ensuring exhaustiveness. They're commonly used in design patterns like the MVI (Model-View-Intent) pattern in Android to represent distinct states or intents.

  1. Subclasses and inheritance in Kotlin sealed classes:

    • Declare a sealed class with specific subclasses.
    sealed class Result {
        data class Success(val data: String) : Result()
        data class Error(val message: String) : Result()
    }
    
  2. When to use sealed classes in Kotlin:

    • Use sealed classes when you have a limited set of subclasses that represent distinct states.
    fun processResult(result: Result) {
        when (result) {
            is Result.Success -> handleSuccess(result.data)
            is Result.Error -> handleError(result.message)
        }
    }
    
  3. Pattern matching with sealed classes in Kotlin:

    • Leverage pattern matching to handle different subclasses.
    when (result) {
        is Result.Success -> handleSuccess(result.data)
        is Result.Error -> handleError(result.message)
    }
    
  4. Sealed classes vs enums in Kotlin:

    • Choose sealed classes when you need to attach data to each state.
    sealed class Result {
        data class Success(val data: String) : Result()
        data class Error(val message: String) : Result()
    }
    
  5. Companion objects in Kotlin sealed classes:

    • Utilize companion objects for additional functionality.
    sealed class Result {
        data class Success(val data: String) : Result() {
            companion object {
                fun createDefault() = Success("Default")
            }
        }
        data class Error(val message: String) : Result()
    }
    
  6. Using sealed classes for state management in Kotlin:

    • Represent different states with sealed classes for cleaner state management.
    sealed class ViewState {
        object Loading : ViewState()
        data class Success(val data: String) : ViewState()
        data class Error(val message: String) : ViewState()
    }
    
  7. Sealed classes and exhaustive when expressions in Kotlin:

    • Leverage the exhaustive nature of when expressions for better code safety.
    when (result) {
        is Result.Success -> handleSuccess(result.data)
        is Result.Error -> handleError(result.message)
    }
    
  8. Sealed classes and polymorphism in Kotlin:

    • Embrace polymorphism for flexibility in handling different subclasses.
    fun processResult(result: Result) {
        // Polymorphic behavior
        result.handle()
    }
    
  9. Sealed classes and data classes in Kotlin:

    • Combine sealed classes with data classes for concise representation.
    sealed class Result {
        data class Success(val data: String) : Result()
        data class Error(val message: String) : Result()
    }
    
  10. Sealed classes in Kotlin and Java interoperability:

    • Use sealed classes in Kotlin and seamlessly interact with Java code.
    // Java code
    public class JavaClass {
        public void processResult(Result result) {
            // Handle Result
        }
    }