Kotlin Sealed Classes
In this Android tutorial, see how to use Kotlin sealed classes to create limited hierarchies that act like enums but allow you to create multiple instances. By Joe Howard.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin Sealed Classes
10 mins
Unlike most other object-oriented languages, Kotlin classes are final
by default. This goes along with a principle from Effective Java, 3rd edition, “Design and document for inheritance or else prohibit it.” In Kotlin, you must explicitly mark a class open
in order to allow inheritance from it.
Falling in a sense in-between these two extremes of open
and final
, there is a form of inheritance available in Kotlin named Sealed Classes. A sealed class has a limited number of direct subclasses, all defined in the same file as the sealed class itself. Sealed classes let you take advantage of some of the flexibility of inheritance, without ending up with a massive inheritance tree. Sealed classes act very much like a more powerful form of enum classes, with the sealed class subtypes being the cases of the enum. Unlike enum cases, however, sealed class subtypes can have multiple instances.
Sealed classes themselves are abstract, so you can’t instantiate an instance of the sealed class directly, only the subclasses. Sealed classes can also have abstract members, which must be implemented by all subclasses of the sealed class. You can create indirect subclasses outside the file where the sealed class is declared, by inheriting from one of the subclasses, but this usually doesn’t end up working very well. In this tutorial you’ll see these properties of sealed classes in action in a currency converter app.
The material in this tutorial was adapted from a section of our book Kotlin Apprentice written by Ellen Shapiro.
Getting Started
Use the Download Materials button at the top or bottom of the tutorial to download and unzip the sample code for this tutorial. Open the starter project in Android Studio 3.2.1 or later.
Once the Gradle build completes, build and run the starter app.
There are two fields for a low amount of money and a high amount of money, and a dropdown to choose a currency for the amounts, either US Dollars, Euro, or a Cryptocurrency. There’s also a convert button to show the equivalent amounts in US Dollars.
Creating a Sealed Class
To begin working with sealed classes, right-click on the app main package and choose New ▸ Kotlin File/Class name it AcceptedCurrency.
Now add a new class to the file and mark it with the sealed
keyword to make it a sealed class.
sealed class AcceptedCurrency
Then add three subclasses directly to same file:
class Dollar : AcceptedCurrency()
class Euro : AcceptedCurrency()
class Crypto : AcceptedCurrency()
Unlike early versions of Kotlin, since version 1.1 the subclasses only need to be in the same file as the parent, and no longer need to be within its body.
Open up ConverterActivity
and update the list of currencies to be a list of AcceptedCurrency
subtype instances and not strings.
private val currencies = listOf(Dollar(), Euro(), Crypto())
You also need to change the type of the ArrayAdapter for the spinner dropdown.
val adapter = ArrayAdapter<AcceptedCurrency>(...)
Build and run and the app.
Looking at the dropdown, you no longer see strings. If we were using enum values in the dropdown, enum classes have a name property that would be used in its string version. For sealed classes, you lose things like a nice name and ordering.
Sealed Class Properties
Fortunately, sealed classes can have non-abstract properties with custom getters, and can also take advantage of when
expressions.
Inside the AcceptedCurrency
sealed class body, add a name property with a custom getter:
sealed class AcceptedCurrency {
val name: String
get() = when (this) {
is Euro -> "Euro"
is Dollar -> "Dollars"
is Crypto -> "NerdCoin"
}
}
Like for an enum, the when
expression needs to cover all the possible sealed class subtypes, or else you’ll get a compiler error.
Switch the adapter in ConverterActivity
back to a String
type and use map
to map each currency to its name for the dropdown:
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,
currencies.map { it.name })
Build and run the app, and you see the sealed class names again in the dropdown.
Abstract Properties
Imagine that the company for this app is U.S.-based, so they want to know how much each of these currencies is worth in US Dollars.
Update the AcceptedCurrency
sealed class to add an abstract val
property named valueInDollars
, and then override it in the all three declared subclasses:
abstract val valueInDollars: Double
class Dollar : AcceptedCurrency() {
override val valueInDollars = 1.0
}
class Euro : AcceptedCurrency() {
override val valueInDollars = 1.25
}
class Crypto : AcceptedCurrency() {
override val valueInDollars = 2534.92
}
The valueInDollars
property captures the exchange rate between the currencies.
It would probably also help to know how much of a currency is being passed around with a single instance. You can add non-abstract properties to a sealed class, as long as you provide them with an initial value.
Right below your abstract declaration of valueInDollars
, add a new variable amount
and initialize it to zero:
var amount: Double = 0.0
Now that you have a place to store the value of a particular currency, you can calculate the total value of the accepted currency. You’ll do that by adding a non-abstract function to your sealed class.
Since every AcceptedCurrency
subclass must provide a valueInDollars
property, and all subclasses have access to the amount
property you just added, you can use those at the AcceptedCurrency
level to provide the same functionality across all classes.
Below the name
property, add a new function to calculate the total value in dollars of a given currency:
fun totalValueInDollars(): Double {
return amount * valueInDollars
}
Now go back to ConverterActivity
and add a function that will pick the current currency based on the selection in the spinner dropdown:
private fun currencyFromSelection() =
when (currencies[currency.selectedItemPosition]) {
is Dollar -> Dollar()
is Euro -> Euro()
is Crypto -> Crypto()
}
Now that you can determine the desired currency based on the selection, you want to be able to convert the amounts entered by the user into that currency. Since there are two amounts, you’ll need to create multiple instances of a currency type.
Thankfully, sealed classes support the creation of multiple instances of the subtypes.