Data Classes & Sealed Classes
Use data classes for value-based equality and copy(), and sealed classes for exhaustive type hierarchies and clean state modeling.
Data Classes & Sealed Classes is a free Android Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Are Data Classes?
A data class in Kotlin is a class whose primary purpose is to hold data. The compiler automatically generates useful methods for you:
equals()— compare two instances by valuehashCode()— consistent hash based on propertiestoString()— readable string representationcopy()— create a modified copy- Destructuring support
Declaring a Data Class
Add the data keyword before class. All properties must be in the primary constructor:
data class User(val id: Int, val name: String, val email: String)
fun main() {
val u1 = User(1, "Alice", "alice@example.com")
val u2 = User(1, "Alice", "alice@example.com")
println(u1) // User(id=1, name=Alice, email=alice@example.com)
println(u1 == u2) // true (value equality)
println(u1 === u2) // false (different objects)
}copy() — Modify One Field
copy() creates a new instance with some properties changed. The original is not modified (immutability):
data class Product(val name: String, val price: Double, val inStock: Boolean)
fun main() {
val original = Product("Laptop", 999.0, true)
// Only change the price; keep everything else
val onSale = original.copy(price = 799.0)
println(original) // Product(name=Laptop, price=999.0, inStock=true)
println(onSale) // Product(name=Laptop, price=799.0, inStock=true)
}Destructuring Declarations
Data classes support destructuring — unpack properties into variables using componentN() functions generated by the compiler:
data class Point(val x: Int, val y: Int)
fun main() {
val point = Point(10, 20)
// Destructure into separate variables
val (x, y) = point
println("x=$x, y=$y") // x=10, y=20
// Also works in for loops with maps
val scores = mapOf("Alice" to 95, "Bob" to 88)
for ((name, score) in scores) {
println("$name: $score")
}
}What Are Sealed Classes?
A sealed class is a restricted class hierarchy — all subclasses must be defined in the same file. This makes the set of possible types known at compile time.
Use sealed classes to represent a finite set of states, like network results, UI states, or navigation events.
Sealed Class Syntax
Declare subclasses directly inside or in the same file:
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val message: String, val code: Int = 0) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}
fun handle(result: NetworkResult<String>) {
when (result) {
is NetworkResult.Success -> println("Data: ${result.data}")
is NetworkResult.Error -> println("Error ${result.code}: ${result.message}")
NetworkResult.Loading -> println("Loading...")
}
}Sealed Class + when (Exhaustive)
The power of sealed classes: when is exhaustive. The compiler forces you to handle every subtype — no forgotten cases:
sealed class UiState {
object Loading : UiState()
data class Success(val message: String) : UiState()
data class Error(val error: String) : UiState()
}
fun render(state: UiState): String = when (state) {
is UiState.Loading -> "Showing spinner"
is UiState.Success -> "Show: ${state.message}"
is UiState.Error -> "Error: ${state.error}"
// Compiler error if you remove any branch!
}
fun main() {
println(render(UiState.Loading))
println(render(UiState.Success("Loaded!")))
}Sealed Class vs Enum
Both represent a fixed set of values, but:
- Enum — each constant is a single instance, no extra data per variant
- Sealed class — each variant can carry different data, be a class or object
Use enum when variants need no data. Use sealed class when variants need different properties.
Object Declarations (Singletons)
object creates a singleton — one instance, created lazily, thread-safe:
object AppConfig {
const val BASE_URL = "https://api.example.com"
const val TIMEOUT = 30L
fun buildUrl(path: String) = "$BASE_URL/$path"
}
fun main() {
println(AppConfig.BASE_URL) // https://api.example.com
println(AppConfig.buildUrl("users")) // https://api.example.com/users
}Companion Objects
A companion object lives inside a class and provides class-level members (like Java's static). Commonly used for factory methods:
class Token private constructor(val value: String) {
companion object {
fun create(raw: String): Token {
val cleaned = raw.trim()
require(cleaned.isNotEmpty()) { "Token must not be blank" }
return Token(cleaned)
}
}
}
fun main() {
val token = Token.create(" abc123 ")
println(token.value) // abc123
}Data Classes in a Real App
Combine data classes with sealed classes for clean state modeling:
data class Post(val id: Int, val title: String, val body: String)
sealed class PostState {
object Loading : PostState()
data class Loaded(val posts: List<Post>) : PostState()
data class Failed(val reason: String) : PostState()
}
fun showState(state: PostState) = when (state) {
PostState.Loading -> println("Fetching posts...")
is PostState.Loaded -> println("${state.posts.size} posts loaded")
is PostState.Failed -> println("Failed: ${state.reason}")
}
fun main() {
showState(PostState.Loaded(listOf(
Post(1, "Hello", "World"),
Post(2, "Kotlin", "Rocks")
)))
}Quick Check
Which statement best describes what makes sealed classes different from regular abstract classes?
Recap: Data & Sealed Classes
You now have two powerful Kotlin tools:
- data class — value-based equality,
copy(), destructuring, auto-generatedtoString() - sealed class — finite type hierarchy, exhaustive
when, each variant can carry its own data - object — singleton, companion objects for factory methods
Together, these patterns replace Java boilerplate and enable clean, safe state modeling.
Frequently asked questions
Is the “Data Classes & Sealed Classes” lesson free?
Yes — the full text of “Data Classes & Sealed Classes” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “Data Classes & Sealed Classes”?
Use data classes for value-based equality and copy(), and sealed classes for exhaustive type hierarchies and clean state modeling. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Data Classes & Sealed Classes” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Null Safety
- Classes & Objects
- Data Classes & Sealed Classes
- Extension Functions & Higher-Order Functions