Kotlin Advanced Comprehensive Cheat Sheet
Table of Contents
- Advanced Language Features
- Coroutines & Structured Concurrency
- Flows & Reactive Programming
- Channels & Concurrent Communication
- DSL Builders
- Delegation & Property Delegates
- Advanced Generics
- Android Development Patterns
- Jetpack Compose
- Kotlin Multiplatform (KMP)
- Ktor Server & Client
- Kotlinx Serialization
- Testing with Kotest
- Performance Optimization
- Learning Resources
Advanced Language Features
Extension Functions & Properties
// ===== EXTENSION FUNCTIONS =====
fun String.isValidEmail(): Boolean {
val emailRegex = Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
return emailRegex.matches(this)
}
fun List.second(): T {
if (size < 2) throw NoSuchElementException("List has less than 2 elements")
return this[1]
}
fun Int.times(action: (Int) -> Unit) {
repeat(this) { action(it) }
}
// Usage
val email = "[email protected]"
println(email.isValidEmail()) // true
val list = listOf(1, 2, 3, 4)
println(list.second()) // 2
5.times { println("Iteration $it") }
// ===== EXTENSION PROPERTIES =====
val String.firstChar: Char
get() = this[0]
val List.sum: Int
get() = this.sum()
// With backing field (not allowed - must use function)
// Extensions can't have state
val List.lastIndex: Int
get() = size - 1
// ===== NULLABLE RECEIVER =====
fun String?.orEmpty(): String = this ?: ""
fun Any?.toStringOrEmpty(): String = this?.toString() ?: ""
// Usage
val nullString: String? = null
println(nullString.orEmpty()) // ""
// ===== COMPANION OBJECT EXTENSIONS =====
class User(val name: String) {
companion object
}
fun User.Companion.create(name: String): User {
return User(name)
}
// Usage
val user = User.create("Alice")
// ===== INFIX FUNCTIONS =====
infix fun Int.pow(exponent: Int): Int {
return Math.pow(this.toDouble(), exponent.toDouble()).toInt()
}
infix fun String.shouldBe(expected: String) {
if (this != expected) throw AssertionError("Expected $expected but got $this")
}
// Usage
val result = 2 pow 3 // 8
"hello" shouldBe "hello"
// ===== OPERATOR OVERLOADING =====
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
operator fun minus(other: Vector) = Vector(x - other.x, y - other.y)
operator fun times(scalar: Int) = Vector(x * scalar, y * scalar)
operator fun unaryMinus() = Vector(-x, -y)
operator fun get(index: Int) = when (index) {
0 -> x
1 -> y
else -> throw IndexOutOfBoundsException()
}
}
// Usage
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
val v3 = v1 + v2 // Vector(4, 6)
val v4 = v1 * 3 // Vector(3, 6)
val v5 = -v1 // Vector(-1, -2)
println(v1[0]) // 1
// ===== INVOKE OPERATOR =====
class Greeter(val greeting: String) {
operator fun invoke(name: String) = "$greeting, $name!"
}
// Usage
val greeter = Greeter("Hello")
println(greeter("Alice")) // "Hello, Alice!"
// ===== DESTRUCTURING =====
data class User(val id: Int, val name: String, val email: String)
val user = User(1, "Alice", "[email protected]")
val (id, name, email) = user
// Custom destructuring
class Result(val success: Boolean, val data: String) {
operator fun component1() = success
operator fun component2() = data
}
val (success, data) = Result(true, "Done")
// ===== SEALED CLASSES & WHEN EXHAUSTIVE =====
sealed class Result {
data class Success(val data: T) : Result()
data class Error(val message: String) : Result()
data object Loading : Result()
}
fun handleResult(result: Result) = when (result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
Result.Loading -> println("Loading...")
// No else needed - exhaustive!
}
// ===== INLINE CLASSES (Value Classes) =====
@JvmInline
value class UserId(val value: Int)
@JvmInline
value class Email(val value: String) {
init {
require(value.contains("@")) { "Invalid email" }
}
}
// No runtime overhead - unwrapped at compile time
fun getUser(id: UserId): User { /* ... */ }
val userId = UserId(123)
val email = Email("[email protected]")
// ===== TYPE ALIASES =====
typealias UserMap = Map
typealias ClickHandler = (View) -> Unit
typealias Predicate = (T) -> Boolean
val users: UserMap = mapOf(1 to User(1, "Alice", "[email protected]"))
val onClick: ClickHandler = { view -> println("Clicked") }
val isEven: Predicate = { it % 2 == 0 }
Higher-Order Functions & Lambdas
// ===== FUNCTION TYPES =====
val sum: (Int, Int) -> Int = { a, b -> a + b }
val greet: (String) -> Unit = { name -> println("Hello, $name") }
val transform: (String) -> String = { it.uppercase() }
// ===== HIGHER-ORDER FUNCTIONS =====
fun List.customFilter(predicate: (T) -> Boolean): List {
val result = mutableListOf()
for (item in this) {
if (predicate(item)) {
result.add(item)
}
}
return result
}
fun List.customMap(transform: (T) -> R): List {
return mutableListOf().apply {
for (item in this@customMap) {
add(transform(item))
}
}
}
// ===== INLINE FUNCTIONS =====
inline fun measureTime(block: () -> T): Pair {
val start = System.currentTimeMillis()
val result = block()
val time = System.currentTimeMillis() - start
return result to time
}
// Usage - no lambda allocation overhead
val (result, time) = measureTime {
// Expensive operation
Thread.sleep(1000)
"Done"
}
// ===== NOINLINE & CROSSINLINE =====
inline fun performOperation(
inline operation: () -> Unit,
noinline callback: () -> Unit // Can be stored/passed
) {
operation()
callbacks.add(callback) // OK because noinline
}
inline fun runAsync(crossinline block: () -> Unit) {
thread {
block() // OK because crossinline (no non-local returns)
}
}
// ===== REIFIED TYPE PARAMETERS =====
inline fun Any.isInstanceOf(): Boolean {
return this is T
}
inline fun Gson.fromJson(json: String): T {
return fromJson(json, T::class.java)
}
// Usage
val obj: Any = "Hello"
println(obj.isInstanceOf()) // true
val json = """{"name":"Alice"}"""
val user = Gson().fromJson(json)
// ===== SCOPE FUNCTIONS =====
// let - transform object, null-safe calls
val length = "Hello".let { it.length } // 5
val result = nullableString?.let { it.uppercase() } ?: "DEFAULT"
// run - execute code block with context
val result = "Hello".run {
println(this)
length
}
// with - non-extension version of run
val result = with(StringBuilder()) {
append("Hello")
append(" ")
append("World")
toString()
}
// apply - configure object
val user = User().apply {
name = "Alice"
email = "[email protected]"
}
// also - perform additional operations
val numbers = mutableListOf(1, 2, 3).also {
println("Before: $it")
}.also {
it.add(4)
}
// ===== FUNCTION REFERENCES =====
fun isEven(num: Int) = num % 2 == 0
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter(::isEven)
// Member reference
data class User(val name: String)
val users = listOf(User("Alice"), User("Bob"))
val names = users.map(User::name)
// Constructor reference
val userCreator: (String) -> User = ::User
// ===== TAIL RECURSION =====
tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
return if (n <= 1) accumulator
else factorial(n - 1, n * accumulator)
}
// Converted to iterative loop by compiler - no stack overflow
println(factorial(10000))
Pro Tip: Use inline functions with reified type parameters to avoid reflection
overhead. Value classes (inline classes) provide type safety without runtime overhead. Prefer
extension functions over utility classes for better readability and discoverability.
Coroutines & Structured Concurrency
Coroutine Basics
// ===== SUSPEND FUNCTIONS =====
suspend fun fetchUser(id: Int): User {
delay(1000) // Suspends coroutine, doesn't block thread
return User(id, "Alice", "[email protected]")
}
suspend fun fetchOrders(userId: Int): List {
delay(500)
return listOf(Order(1, 100.0), Order(2, 200.0))
}
// ===== LAUNCHING COROUTINES =====
// launch - fire and forget
fun main() = runBlocking {
launch {
delay(1000)
println("World!")
}
println("Hello,")
}
// async - returns Deferred
suspend fun getUserData(userId: Int): UserData = coroutineScope {
val userDeferred = async { fetchUser(userId) }
val ordersDeferred = async { fetchOrders(userId) }
UserData(
user = userDeferred.await(),
orders = ordersDeferred.await()
)
}
// ===== COROUTINE BUILDERS =====
// runBlocking - blocks current thread
fun main() = runBlocking {
println("Start")
delay(1000)
println("End")
}
// coroutineScope - suspends, doesn't block
suspend fun doWork() = coroutineScope {
launch { /* work 1 */ }
launch { /* work 2 */ }
// Waits for all children
}
// supervisorScope - children failures don't cancel siblings
suspend fun doWorkWithErrorHandling() = supervisorScope {
val job1 = launch {
delay(100)
throw Exception("Job 1 failed")
}
val job2 = launch {
delay(200)
println("Job 2 completed") // Still runs!
}
}
// ===== COROUTINE CONTEXT & DISPATCHERS =====
// Dispatchers.Default - CPU-intensive work
launch(Dispatchers.Default) {
val result = calculatePrimes(1000000)
}
// Dispatchers.IO - I/O operations
launch(Dispatchers.IO) {
val data = File("data.txt").readText()
}
// Dispatchers.Main - UI updates (Android)
launch(Dispatchers.Main) {
textView.text = "Updated"
}
// Dispatchers.Unconfined - not recommended
launch(Dispatchers.Unconfined) {
// Starts in caller thread, resumes in arbitrary thread
}
// ===== SWITCHING CONTEXTS =====
suspend fun getData(): String {
val result = withContext(Dispatchers.IO) {
// Runs on IO dispatcher
File("data.txt").readText()
}
// Back to previous dispatcher
return result
}
// ===== CANCELLATION =====
val job = launch {
repeat(1000) { i ->
println("Job: I'm working $i")
delay(500)
}
}
delay(1300)
println("Cancelling job")
job.cancel() // Cancel the coroutine
job.join() // Wait for cancellation
// ===== CANCELLATION COOPERATION =====
val job = launch {
repeat(1000) { i ->
// Check if cancelled
if (!isActive) return@launch
// Or use ensureActive()
ensureActive()
println("Working $i")
delay(500)
}
}
// ===== TIMEOUT =====
try {
withTimeout(1000) {
delay(2000) // Will throw TimeoutCancellationException
println("Done")
}
} catch (e: TimeoutCancellationException) {
println("Timed out")
}
// Returns null instead of throwing
val result = withTimeoutOrNull(1000) {
delay(2000)
"Done"
} // null
// ===== EXCEPTION HANDLING =====
// launch - use CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught exception: $exception")
}
val scope = CoroutineScope(Dispatchers.Default + handler)
scope.launch {
throw Exception("Error!") // Caught by handler
}
// async - exceptions stored in Deferred
val deferred = async {
throw Exception("Error!")
}
try {
deferred.await() // Exception thrown here
} catch (e: Exception) {
println("Caught: $e")
}
// ===== STRUCTURED CONCURRENCY =====
class UserRepository {
private val scope = CoroutineScope(
SupervisorJob() + Dispatchers.Default
)
fun fetchUsers() {
scope.launch {
val users = async { fetchFromDb() }
val cache = async { updateCache() }
// Both must complete
combine(users.await(), cache.await())
}
}
fun cleanup() {
scope.cancel() // Cancels all child coroutines
}
}
// ===== PARALLEL DECOMPOSITION =====
suspend fun loadUserProfile(userId: Int): UserProfile = coroutineScope {
val user = async { fetchUser(userId) }
val posts = async { fetchPosts(userId) }
val friends = async { fetchFriends(userId) }
UserProfile(
user = user.await(),
posts = posts.await(),
friends = friends.await()
)
}
// ===== SELECT EXPRESSION (First to complete) =====
suspend fun fetchFromMultipleSources(): String {
val source1 = async { fetchFromServer1() }
val source2 = async { fetchFromServer2() }
return select {
source1.onAwait { it }
source2.onAwait { it }
}
}
// ===== MUTEX (Mutual Exclusion) =====
val mutex = Mutex()
var counter = 0
suspend fun increment() {
mutex.withLock {
counter++
}
}
// Concurrent-safe without blocking
repeat(1000) {
launch {
increment()
}
}
Coroutine Execution Flow
Thread-Blocking vs Suspension:
─────────────────────────────────────────
Thread.sleep(1000):
Thread: [████████████████████] (blocked)
^ ^
0ms 1000ms
delay(1000):
Thread: [Task1]─────────[Continue]
└─(suspended)────┘
^ ^
0ms 1000ms
(Thread free for other work)
Parallel Execution with async:
─────────────────────────────────────────
launch {
val user = async { fetchUser() } ───┐
val orders = async { fetchOrders() } ──┤
val profile = async { fetchProfile() }─┤
├─ Run in parallel
combine( │
user.await(), ──┤
orders.await(), ──┤
profile.await() ──┘
)
}
Flows & Reactive Programming
Flow Basics
// ===== CREATING FLOWS =====
// Simple flow
fun numbers(): Flow = flow {
for (i in 1..5) {
delay(100)
emit(i)
}
}
// From collection
val flow = listOf(1, 2, 3).asFlow()
// From channel
val channel = Channel()
val flow = channel.receiveAsFlow()
// flowOf
val flow = flowOf(1, 2, 3, 4, 5)
// ===== COLLECTING FLOWS =====
suspend fun collectNumbers() {
numbers().collect { value ->
println(value)
}
}
// Collect with index
numbers().collectIndexed { index, value ->
println("[$index] $value")
}
// Collect latest (cancels previous collection)
flow.collectLatest { value ->
delay(300)
println(value) // May skip values
}
// ===== FLOW OPERATORS =====
// map - transform values
val squared = numbers().map { it * it }
// filter - filter values
val evens = numbers().filter { it % 2 == 0 }
// transform - multiple emissions per value
val transformed = numbers().transform { value ->
emit("Value: $value")
emit("Squared: ${value * value}")
}
// take - limit emissions
val firstThree = numbers().take(3)
// drop - skip emissions
val afterTwo = numbers().drop(2)
// distinctUntilChanged - skip consecutive duplicates
val distinct = flowOf(1, 1, 2, 2, 3).distinctUntilChanged() // 1, 2, 3
// debounce - only emit after quiet period
searchQuery.debounce(300).collect { query ->
search(query)
}
// sample - emit latest value periodically
flow.sample(1000).collect { /* ... */ }
// ===== COMBINING FLOWS =====
// zip - combine values pairwise
val combined = flow1.zip(flow2) { a, b -> a + b }
// combine - combine latest values
val result = flow1.combine(flow2) { a, b -> a + b }
// flatMapConcat - sequential flattening
val concatenated = flow.flatMapConcat { value ->
flowOf("${value}a", "${value}b")
}
// flatMapMerge - concurrent flattening
val merged = flow.flatMapMerge { value ->
flow { emit(fetchData(value)) }
}
// flatMapLatest - cancel previous, start new
val latest = searchQuery.flatMapLatest { query ->
searchDatabase(query)
}
// ===== EXCEPTION HANDLING =====
// catch - handle upstream exceptions
numbers()
.map { 100 / it }
.catch { e ->
println("Caught exception: $e")
emit(-1) // Emit fallback value
}
.collect { println(it) }
// onCompletion - called when flow completes
flow
.onCompletion { cause ->
if (cause != null) println("Flow failed: $cause")
else println("Flow completed successfully")
}
.collect { /* ... */ }
// ===== BUFFERING =====
// buffer - don't wait for collector
flow {
repeat(5) {
delay(100)
emit(it)
}
}
.buffer()
.collect { value ->
delay(300) // Slow collector
println(value)
}
// conflate - keep only latest
flow.conflate().collect { /* ... */ }
// ===== STATE FLOW (Hot Flow) =====
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow = _uiState.asStateFlow()
fun loadUser(id: Int) {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val user = userRepository.getUser(id)
_uiState.value = UiState.Success(user)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
// Collect in UI
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showUser(state.user)
is UiState.Error -> showError(state.message)
}
}
}
// ===== SHARED FLOW (Hot Flow, Multiple Subscribers) =====
class EventBus {
private val _events = MutableSharedFlow()
val events: SharedFlow = _events.asSharedFlow()
suspend fun emit(event: Event) {
_events.emit(event)
}
}
// Configure replay and buffer
private val _events = MutableSharedFlow(
replay = 1, // Replay last N events to new subscribers
extraBufferCapacity = 64, // Buffer overflow
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// ===== FLOW CONTEXT =====
fun fetchData(): Flow = flow {
// Runs on context where collect is called
emit(loadFromDatabase())
}.flowOn(Dispatchers.IO) // Changes context for upstream
// Usage
withContext(Dispatchers.Main) {
fetchData().collect { data ->
// Runs on Main
updateUI(data)
}
}
// ===== REAL-WORLD EXAMPLE: SEARCH WITH DEBOUNCE =====
class SearchViewModel : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchResults: StateFlow> = _searchQuery
.debounce(300)
.filter { it.length >= 3 }
.distinctUntilChanged()
.flatMapLatest { query ->
searchRepository.search(query)
.catch { emit(emptyList()) }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun onSearchQueryChanged(query: String) {
_searchQuery.value = query
}
}
// ===== CHANNEL TO FLOW =====
val channel = Channel()
val flow = channel.receiveAsFlow()
launch {
repeat(5) {
channel.send(it)
}
channel.close()
}
flow.collect { println(it) }
Flow vs Sequence vs List
| Type | Execution | Async Support | Use Case |
|---|---|---|---|
| List | Eager (all at once) | No | Small, in-memory collections |
| Sequence | Lazy (on demand) | No | Large collections, sync ops |
| Flow | Lazy (on demand) | Yes (suspend) | Async streams, reactive data |
Pro Tip: Use StateFlow for state that always has a value (like UI state).
Use SharedFlow for events that may have multiple subscribers. Use regular Flow for cold streams
that start emitting when collected. Remember to use .stateIn() or .shareIn() to convert cold
flows to hot flows when needed.
Channels & Concurrent Communication
Channel Types & Patterns
// ===== BASIC CHANNEL =====
val channel = Channel()
launch {
for (i in 1..5) {
channel.send(i) // Suspends until received
}
channel.close()
}
launch {
for (value in channel) { // Iterate until closed
println(value)
}
}
// ===== CHANNEL TYPES =====
// Rendezvous (default, capacity = 0)
val rendezvous = Channel() // send waits for receive
// Buffered (capacity = n)
val buffered = Channel(capacity = 10) // send only suspends when full
// Unlimited
val unlimited = Channel(Channel.UNLIMITED) // never suspends on send
// Conflated (keeps only latest)
val conflated = Channel(Channel.CONFLATED) // overwrites unread values
// ===== PRODUCER-CONSUMER PATTERN =====
fun CoroutineScope.produceNumbers() = produce {
var x = 1
while (true) {
send(x++)
delay(100)
}
}
fun CoroutineScope.square(numbers: ReceiveChannel) = produce {
for (x in numbers) {
send(x * x)
}
}
// Usage
val numbers = produceNumbers()
val squares = square(numbers)
repeat(5) {
println(squares.receive())
}
coroutineContext.cancelChildren()
// ===== FAN-OUT (Multiple consumers) =====
fun CoroutineScope.produceNumbers() = produce {
var x = 1
while (true) {
send(x++)
delay(100)
}
}
val producer = produceNumbers()
// Multiple consumers
repeat(5) { id ->
launch {
for (msg in producer) {
println("Consumer #$id received $msg")
}
}
}
// ===== FAN-IN (Multiple producers) =====
suspend fun sendString(channel: SendChannel, s: String, time: Long) {
while (true) {
delay(time)
channel.send(s)
}
}
val channel = Channel()
launch { sendString(channel, "foo", 200) }
launch { sendString(channel, "bar", 500) }
repeat(10) {
println(channel.receive())
}
coroutineContext.cancelChildren()
// ===== SELECT EXPRESSION WITH CHANNELS =====
suspend fun selectExample() {
val channel1 = Channel()
val channel2 = Channel()
launch {
delay(100)
channel1.send("from channel 1")
}
launch {
delay(200)
channel2.send("from channel 2")
}
// Receive from first available
val result = select {
channel1.onReceive { it }
channel2.onReceive { it }
}
println(result) // "from channel 1" (faster)
}
// ===== ACTOR PATTERN =====
sealed class CounterMsg {
object Increment : CounterMsg()
object Decrement : CounterMsg()
data class Get(val response: CompletableDeferred) : CounterMsg()
}
fun CoroutineScope.counterActor() = actor {
var counter = 0
for (msg in channel) {
when (msg) {
is CounterMsg.Increment -> counter++
is CounterMsg.Decrement -> counter--
is CounterMsg.Get -> msg.response.complete(counter)
}
}
}
// Usage
val counter = counterActor()
counter.send(CounterMsg.Increment)
counter.send(CounterMsg.Increment)
counter.send(CounterMsg.Decrement)
val response = CompletableDeferred()
counter.send(CounterMsg.Get(response))
println("Counter = ${response.await()}") // Counter = 1
counter.close()
// ===== BROADCAST CHANNEL (Deprecated - use SharedFlow) =====
// Old way (deprecated)
@OptIn(ObsoleteCoroutinesApi::class)
val broadcast = BroadcastChannel(10)
// New way - use SharedFlow
val sharedFlow = MutableSharedFlow()
// ===== TICKER CHANNEL =====
val tickerChannel = ticker(delayMillis = 1000, initialDelayMillis = 0)
repeat(5) {
tickerChannel.receive()
println("Tick")
}
tickerChannel.cancel()
// ===== PIPELINE PATTERN =====
fun CoroutineScope.numbers() = produce {
var x = 1
while (true) send(x++)
}
fun CoroutineScope.filter(
numbers: ReceiveChannel,
predicate: (Int) -> Boolean
) = produce {
for (x in numbers) {
if (predicate(x)) send(x)
}
}
fun CoroutineScope.map(
numbers: ReceiveChannel,
transform: (Int) -> Int
) = produce {
for (x in numbers) send(transform(x))
}
// Build pipeline
val pipeline = numbers()
.let { filter(it) { it % 2 == 0 } }
.let { map(it) { it * it } }
// Consume
repeat(10) {
println(pipeline.receive())
}
coroutineContext.cancelChildren()
Common Pitfall: Channels are hot - they start producing immediately. Flows
are cold - they only start when collected. Don't forget to close() channels when done, or use
produce {} builder which closes automatically. Prefer SharedFlow/StateFlow over BroadcastChannel
which is now deprecated.
DSL Builders
Type-Safe Builders
// ===== HTML DSL =====
@DslMarker
annotation class HtmlDsl
@HtmlDsl
interface Element {
fun render(): String
}
@HtmlDsl
class HTML : Element {
private val children = mutableListOf()
fun head(init: Head.() -> Unit) {
children.add(Head().apply(init))
}
fun body(init: Body.() -> Unit) {
children.add(Body().apply(init))
}
override fun render() = "${children.joinToString("") { it.render() }}"
}
@HtmlDsl
class Head : Element {
private val children = mutableListOf()
fun title(text: String) {
children.add(Title(text))
}
override fun render() = "${children.joinToString("") { it.render() }}"
}
class Title(private val text: String) : Element {
override fun render() = "$text "
}
@HtmlDsl
class Body : Element {
private val children = mutableListOf()
fun h1(text: String) {
children.add(H1(text))
}
fun p(text: String) {
children.add(P(text))
}
fun div(cssClass: String = "", init: Div.() -> Unit) {
children.add(Div(cssClass).apply(init))
}
override fun render() = "${children.joinToString("") { it.render() }}"
}
class H1(private val text: String) : Element {
override fun render() = "$text
"
}
class P(private val text: String) : Element {
override fun render() = "$text
"
}
@HtmlDsl
class Div(private val cssClass: String) : Element {
private val children = mutableListOf()
fun p(text: String) {
children.add(P(text))
}
override fun render() {
val classAttr = if (cssClass.isNotEmpty()) " class=\"$cssClass\"" else ""
return "${children.joinToString("") { it.render() }}"
}
}
fun html(init: HTML.() -> Unit): HTML {
return HTML().apply(init)
}
// Usage
val page = html {
head {
title("My Page")
}
body {
h1("Welcome")
p("This is a paragraph")
div("container") {
p("Nested content")
}
}
}
println(page.render())
// ===== SQL DSL =====
@DslMarker
annotation class SqlDsl
@SqlDsl
class SelectQuery {
private val columns = mutableListOf()
private var tableName: String = ""
private val whereClauses = mutableListOf()
private val orderByColumns = mutableListOf()
fun select(vararg cols: String) {
columns.addAll(cols)
}
fun from(table: String) {
tableName = table
}
fun where(clause: String) {
whereClauses.add(clause)
}
fun orderBy(vararg cols: String) {
orderByColumns.addAll(cols)
}
fun build(): String {
val sql = StringBuilder("SELECT ")
sql.append(columns.joinToString(", "))
sql.append(" FROM $tableName")
if (whereClauses.isNotEmpty()) {
sql.append(" WHERE ")
sql.append(whereClauses.joinToString(" AND "))
}
if (orderByColumns.isNotEmpty()) {
sql.append(" ORDER BY ")
sql.append(orderByColumns.joinToString(", "))
}
return sql.toString()
}
}
fun query(init: SelectQuery.() -> Unit): String {
return SelectQuery().apply(init).build()
}
// Usage
val sql = query {
select("id", "name", "email")
from("users")
where("active = true")
where("age > 18")
orderBy("name", "email")
}
// SELECT id, name, email FROM users WHERE active = true AND age > 18 ORDER BY name, email
// ===== CONFIGURATION DSL =====
@DslMarker
annotation class ConfigDsl
@ConfigDsl
class ServerConfig {
var port: Int = 8080
var host: String = "localhost"
var ssl: SslConfig? = null
fun ssl(init: SslConfig.() -> Unit) {
ssl = SslConfig().apply(init)
}
}
@ConfigDsl
class SslConfig {
var keyStore: String = ""
var keyStorePassword: String = ""
}
fun server(init: ServerConfig.() -> Unit): ServerConfig {
return ServerConfig().apply(init)
}
// Usage
val config = server {
port = 9000
host = "0.0.0.0"
ssl {
keyStore = "keystore.jks"
keyStorePassword = "secret"
}
}
// ===== VALIDATION DSL =====
@DslMarker
annotation class ValidationDsl
@ValidationDsl
class ValidationContext {
private val rules = mutableListOf>()
fun rule(message: String, predicate: (T) -> Boolean) {
rules.add(ValidationRule(message, predicate))
}
fun validate(value: T): ValidationResult {
val errors = rules
.filter { !it.predicate(value) }
.map { it.message }
return if (errors.isEmpty()) {
ValidationResult.Valid
} else {
ValidationResult.Invalid(errors)
}
}
}
data class ValidationRule(
val message: String,
val predicate: (T) -> Boolean
)
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(val errors: List) : ValidationResult()
}
fun validator(init: ValidationContext.() -> Unit): (T) -> ValidationResult {
val context = ValidationContext().apply(init)
return { value -> context.validate(value) }
}
// Usage
val emailValidator = validator {
rule("Email cannot be empty") { it.isNotEmpty() }
rule("Email must contain @") { it.contains("@") }
rule("Email must contain domain") { it.split("@").last().contains(".") }
}
val result = emailValidator("[email protected]")
when (result) {
is ValidationResult.Valid -> println("Valid!")
is ValidationResult.Invalid -> println("Errors: ${result.errors}")
}
// ===== ROUTING DSL (Ktor-style) =====
@DslMarker
annotation class RoutingDsl
@RoutingDsl
class Routing {
private val routes = mutableListOf()
fun get(path: String, handler: () -> String) {
routes.add(Route("GET", path, handler))
}
fun post(path: String, handler: () -> String) {
routes.add(Route("POST", path, handler))
}
fun route(path: String, init: Routing.() -> Unit) {
val nested = Routing().apply(init)
routes.addAll(nested.routes.map { it.copy(path = "$path${it.path}") })
}
fun findRoute(method: String, path: String): Route? {
return routes.find { it.method == method && it.path == path }
}
}
data class Route(
val method: String,
val path: String,
val handler: () -> String
)
fun routing(init: Routing.() -> Unit): Routing {
return Routing().apply(init)
}
// Usage
val app = routing {
get("/") { "Home" }
get("/about") { "About" }
route("/api") {
get("/users") { "List users" }
post("/users") { "Create user" }
route("/products") {
get("/") { "List products" }
post("/") { "Create product" }
}
}
}
Pro Tip: Use @DslMarker annotation to prevent nested DSL scopes from calling
methods from outer scopes. This makes DSLs type-safe and prevents confusing code. Leverage
function types with receivers (T.() -> Unit) for clean DSL syntax.
Android Development Patterns
ViewModel & State Management
// ===== VIEWMODEL WITH STATEFLOW =====
sealed class UiState {
object Loading : UiState()
data class Success(val data: List) : UiState()
data class Error(val message: String) : UiState()
}
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow = _uiState.asStateFlow()
init {
loadUsers()
}
fun loadUsers() {
viewModelScope.launch {
_uiState.value = UiState.Loading
try {
val users = repository.getUsers()
_uiState.value = UiState.Success(users)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
fun refresh() {
loadUsers()
}
}
// In Activity/Fragment
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showUsers(state.data)
is UiState.Error -> showError(state.message)
}
}
}
}
}
}
// ===== SINGLE EVENT PATTERN =====
data class UiEvent(
private val message: String,
private val id: String = UUID.randomUUID().toString()
) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): String? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
message
}
}
}
class MyViewModel : ViewModel() {
private val _events = Channel()
val events = _events.receiveAsFlow()
fun triggerEvent() {
viewModelScope.launch {
_events.send(UiEvent("Event triggered"))
}
}
}
// Collect
lifecycleScope.launch {
viewModel.events.collect { event ->
event.getContentIfNotHandled()?.let { message ->
Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
}
}
}
// ===== REPOSITORY PATTERN =====
interface UserRepository {
suspend fun getUsers(): List
suspend fun getUser(id: Int): User
suspend fun createUser(user: User): User
suspend fun updateUser(user: User)
suspend fun deleteUser(id: Int)
}
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : UserRepository {
override suspend fun getUsers(): List = withContext(dispatcher) {
try {
val users = remoteDataSource.fetchUsers()
localDataSource.saveUsers(users)
users
} catch (e: Exception) {
localDataSource.getUsers()
}
}
override suspend fun getUser(id: Int): User = withContext(dispatcher) {
localDataSource.getUser(id) ?: remoteDataSource.fetchUser(id)
}
// ... other methods
}
// ===== USE CASE PATTERN =====
class GetUserUseCase(
private val repository: UserRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend operator fun invoke(userId: Int): Result = withContext(dispatcher) {
try {
val user = repository.getUser(userId)
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// In ViewModel
class UserDetailViewModel(
private val getUserUseCase: GetUserUseCase
) : ViewModel() {
fun loadUser(userId: Int) {
viewModelScope.launch {
getUserUseCase(userId)
.onSuccess { user -> /* handle success */ }
.onFailure { error -> /* handle error */ }
}
}
}
// ===== DEPENDENCY INJECTION WITH HILT =====
@HiltAndroidApp
class MyApplication : Application()
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi {
return retrofit.create(UserApi::class.java)
}
}
@Module
@InstallIn(ViewModelComponent::class)
object RepositoryModule {
@Provides
fun provideUserRepository(
api: UserApi,
dao: UserDao
): UserRepository {
return UserRepositoryImpl(
remoteDataSource = UserRemoteDataSource(api),
localDataSource = UserLocalDataSource(dao)
)
}
}
// Inject in ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
// ...
}
// Inject in Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
}
Learning Resources
Official Documentation
Related Sheets
Tools
- IntelliJ IDEA / Android Studio
- Gradle build system
- Kotlin Playground (online)
- Android Profiler
Learning Path
- Kotlin fundamentals and advanced features
- Coroutines and structured concurrency
- Flow and reactive programming
- Android development with Jetpack
- Jetpack Compose for UI
- Kotlin Multiplatform basics
- Testing and best practices
- Production deployment