Kotlin Training Program

DOWNLOAD APP

FEEDBACK

Sealed class

Sealed class is a class that is open for inheritance only inside the package (folder) that contains it.

Recall that enum classes provide exhaustiveness. But it has some limitations :

In scenarios where we need exhaustiveness feature of enum class & the full flexibility of classes, we can use sealed class.

Ex. - QuizState

Suppose we are developing a Quiz app. At any given time, player might be in one of the following states :

Using Enum class

We can represent these states using an enum class :

 enum class QuizState {
    Ready, Playing, GameOver
}
 

We need the following data members for Playing state :

  • currentQuestion: Question
  • currentQNo: Int
  • totalQuestions: Int
  • score: Int

And following data members for GameOver state :

  • score: Int

We can define them in enum class, but they have to be initialized and we don’t have a constructor to pass initial values :

 enum class QuizState {
    Ready, 
    Playing {
        val currentQuestion: Question; //ERROR!
        val currentQNo: Int; //ERROR!
        val totalQuestions: Int; //ERROR!
        val score: Int; //ERROR!
    }, 
    GameOver {
        val score: Int; //ERROR!
    }
}
 

Turns out Enum class is not a suitable fit for this example.

Using Inheritance

Enum class fall short for this example because we want the features of class like constructors (absent in enum class).

We can define the same example using Inheritance :

 interface QuizState

object Ready: QuizState

class Playing(
    val currentQuestion: Question,
    val currentQNo: Int,
    val totalQuestions: Int,
    val score: Int
): QuizState

class GameOver(
    val score: Int
): QuizState
 

Now that we are not using enum class, we miss the exhaustiveness feature :

 fun getSummary(quizState: QuizState): String {
    return when (quizState) {
        Ready -> "Ready to play!"
        is Playing -> "Playing. Answer the question!"
        is GameOver -> "Game over!"
        else -> error("Invalid state") // Have to define else branch
    }
}
 

We have to define the else branch. So, it’s a source of error when new state is created in future. Lets see how sealed classes help in this case.

Using Sealed class

We can define a sealed class for QuizState as :

 sealed class QuizState {

    object Ready: QuizState()

    class Playing(
        val currentQuestion: Question,
        val currentQNo: Int,
        val totalQuestions: Int,
        val score: Int
    ): QuizState()

    class GameOver(
        val score: Int
    ): QuizState()
}
 

Similar to enum class, sealed class also provide exhaustiveness :

 fun getSummary(quizState: QuizState): String {
    return when (quizState) {
        QuizState.Ready -> "Ready to play!"
        is QuizState.Playing -> "Playing. Answer the question!"
        is QuizState.GameOver -> "Game over!"
    }
}
 

We no longer need an else branch. Moreover, compile time error will appear in case a new state is created in future.

Basics

Defining

We define a sealed class using sealed keyword :

 sealed class /* name */ {
		// Sub-classes can be defined here
}

// Sub-classes can be defined here also
 

Sealed classes are open by default, so we can inherit them directly.

We can define sub classes anywhere inside the package (folder) that contains sealed class. Defining the sub-class outside the package will lead to compile time error.

Following are several ways in which we can define a sealed class & its sub-classes :

  • Inside sealed class (Preferred way)

     sealed class BulbState {
        object Off: BulbState()
        
        class On(
            val color: Int,
            val intensity: Float
        ): BulbState()
    }
     
  • Outside sealed class

     sealed class BulbState
    
    /*
     Following subclasses can also be defined in 
     separate files inside same package (folder) 
     as On.kt and Off.kt
     */
    
    object Off: BulbState()
    
    class On(
        val color: Int,
        val intensity: Float
    ): BulbState()
     

Class Members

Sealed class is a class, so it can have data members as well as member functions. Moreover, we can define abstract functions without defining the class as abstract :

 sealed class BulbState {

    abstract fun toggle(): BulbState

    object Off: BulbState() {
        override fun toggle() = On(0xFFFFFF, 0.5f)
    }

    class On(
        val color: Int,
        val intensity: Float
    ): BulbState() {
        override fun toggle() = Off
    }
}