Examples
Five idiomatic Kotlin snippets: data classes, sealed classes, scope functions, coroutines, and DSL builders.
Five Kotlin recipes
EXAMPLE
// 1) Data class with copy + destructuring
data class Order(val id: String, val customer: String, val totalCents: Long, val status: String)
val o = Order("o1", "alice", 4995, "new")
val paid = o.copy(status = "paid") // immutable update
val (id, customer) = paid // destructuring
// 2) Sealed class for UI state
sealed interface UiState
data object Loading : UiState
data class Loaded(val orders: List<Order>) : UiState
data class Failed(val message: String) : UiState
fun render(state: UiState) = when (state) { // exhaustive match
Loading -> "Loading..."
is Loaded -> "${state.orders.size} orders"
is Failed -> "Error: ${state.message}"
}
// 3) Scope functions — let / apply / run / with / also
val url = "https://example.com"
val response = url.let {
if (it.startsWith("https")) it.uppercase() else null
}
val sb = StringBuilder().apply {
append("Hello, ")
append("world")
}.toString()
val firstWord = "hello world".run {
split(" ").firstOrNull()
}
// 4) Coroutines with Flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun fetchOrders(): List<Order> {
delay(50) // simulate IO
return listOf(o)
}
fun ordersStream(): Flow<List<Order>> = flow {
while (currentCoroutineContext().isActive) {
emit(fetchOrders())
delay(5000)
}
}.flowOn(Dispatchers.IO)
// Usage:
// scope.launch {
// ordersStream().collect { orders -> println(orders) }
// }
// 5) Tiny type-safe DSL (Kotlin builder)
class HtmlElement(val tag: String) {
private val children = mutableListOf<HtmlElement>()
private var text: String = ""
fun text(t: String) { text = t }
operator fun String.unaryPlus() { text += this }
fun child(tag: String, init: HtmlElement.() -> Unit) {
val c = HtmlElement(tag).apply(init)
children += c
}
override fun toString(): String =
"<$tag>${text}${children.joinToString("")}</$tag>"
}
fun html(init: HtmlElement.() -> Unit): HtmlElement =
HtmlElement("html").apply(init)
val doc = html {
child("body") {
child("h1") { +"Hello" }
child("p") { +"World" }
}
}
println(doc)
// ===== Patterns to internalise =====
// - Data classes for value types
// - Sealed classes / interfaces for exhaustive state
// - Scope functions to chain transforms cleanly
// - Flow for streams; suspend fun for single-value async
// - DSL for builders; .apply { } for fluent setup
// ===== Pitfalls =====
// - Overusing scope functions (let / apply / with) makes code unreadable
// - Forgetting .flowOn(Dispatchers.IO) on a Flow doing blocking IO
// - data class equality with floats / nested mutable state
// - DSLs without @DslMarker -> nested receivers leak across scopes
Why it matters
Data class + sealed class + scope functions are the trio that makes Kotlin "Java without the noise". Reach for them by reflex: data class for any value type, sealed when "this can only be one of these N shapes", and scope functions for chained transforms — the resulting code is hard to read in two ways: too much OR too little use.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…