Kotlin

Essential Kotlin syntax, null safety, and idiomatic patterns for modern JVM and Android development.

languages
kotlinjvmandroidmultiplatform

Variables & Types

// Immutable (preferred)
val name: String = "John"
val age = 25  // Type inference

// Mutable
var counter = 0
counter++

// Constants
const val MAX_SIZE = 100  // Compile-time constant

// Nullable types
var nullable: String? = null
val length = nullable?.length  // Safe call
val lengthOrZero = nullable?.length ?: 0  // Elvis operator

// Late initialization
lateinit var lateInit: String

// Lazy initialization
val expensive by lazy { computeValue() }

Null Safety

// Nullable declaration
var name: String? = null

// Safe call operator
val length = name?.length  // Returns null if name is null

// Elvis operator
val length = name?.length ?: 0  // Default if null
val name = input ?: throw IllegalArgumentException("Required")

// Not-null assertion (avoid if possible)
val length = name!!.length  // Throws NPE if null

// Safe cast
val str: String? = value as? String

// Let for null checks
name?.let { 
    println("Name is $it")
}

// Multiple null checks
val result = a?.b?.c?.d

Strings

// String templates
val greeting = "Hello, $name!"
val info = "Age: ${person.age}"

// Multiline strings
val text = """
    |Line 1
    |Line 2
    |Line 3
""".trimMargin()

// String operations
str.length
str.uppercase()
str.lowercase()
str.trim()
str.split(",")
str.replace("old", "new")
str.startsWith("He")
str.endsWith("lo")
str.contains("ell")
str.isEmpty()
str.isBlank()  // Empty or whitespace only
str.isNullOrEmpty()
str.isNullOrBlank()

Collections

// Immutable collections (default)
val list = listOf(1, 2, 3)
val set = setOf("a", "b", "c")
val map = mapOf("key" to "value", "foo" to "bar")

// Mutable collections
val mutableList = mutableListOf(1, 2, 3)
val mutableSet = mutableSetOf("a", "b")
val mutableMap = mutableMapOf("key" to "value")

// List operations
list[0]                      // Get by index
list.first()                 // First element
list.last()                  // Last element
list.getOrNull(10)           // Safe get
list.size
list.isEmpty()
list.contains(1)
list + 4                     // New list with element
list - 1                     // New list without element

// Map operations
map["key"]                   // Get value
map.getOrDefault("key", "default")
map.keys
map.values
map.entries

// Mutable operations
mutableList.add(4)
mutableList.remove(1)
mutableList += 5
mutableMap["newKey"] = "newValue"

Control Flow

// If expression (returns value)
val max = if (a > b) a else b

// When expression (switch on steroids)
val result = when (x) {
    1 -> "one"
    2, 3 -> "two or three"
    in 4..10 -> "between 4 and 10"
    is String -> "it's a string"
    else -> "unknown"
}

// When without argument
when {
    x < 0 -> println("negative")
    x == 0 -> println("zero")
    else -> println("positive")
}

// For loops
for (i in 1..10) { }           // 1 to 10 inclusive
for (i in 1 until 10) { }      // 1 to 9
for (i in 10 downTo 1) { }     // 10 to 1
for (i in 1..10 step 2) { }    // 1, 3, 5, 7, 9
for (item in list) { }
for ((index, value) in list.withIndex()) { }
for ((key, value) in map) { }

// While loops
while (condition) { }
do { } while (condition)

// Ranges
val range = 1..10
val charRange = 'a'..'z'
if (x in 1..10) { }

Functions

// Basic function
fun add(a: Int, b: Int): Int {
    return a + b
}

// Single expression function
fun add(a: Int, b: Int) = a + b

// Default parameters
fun greet(name: String = "World") = "Hello, $name!"

// Named arguments
greet(name = "John")

// Varargs
fun printAll(vararg messages: String) {
    messages.forEach { println(it) }
}

// Extension functions
fun String.addExclamation() = "$this!"
"Hello".addExclamation()  // "Hello!"

// Infix functions
infix fun Int.times(str: String) = str.repeat(this)
2 times "Hi "  // "Hi Hi "

// Higher-order functions
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}
operate(2, 3) { x, y -> x + y }

Lambdas

// Lambda syntax
val sum = { a: Int, b: Int -> a + b }
val square: (Int) -> Int = { it * it }

// Trailing lambda
list.filter { it > 0 }
list.map { it * 2 }

// it - implicit single parameter
list.filter { it > 0 }

// Multiple statements
list.map {
    val doubled = it * 2
    doubled + 1
}

// Lambda with receiver
fun buildString(action: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.action()
    return sb.toString()
}

Classes

// Basic class
class Person(val name: String, var age: Int)

// With init block
class Person(name: String) {
    val name: String
    init {
        this.name = name.uppercase()
    }
}

// Secondary constructor
class Person(val name: String) {
    var age: Int = 0
    
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

// Properties
class Person {
    var name: String = ""
        get() = field.uppercase()
        set(value) {
            field = value.trim()
        }
    
    val isAdult: Boolean
        get() = age >= 18
}

// Inheritance
open class Animal(val name: String) {
    open fun sound() = "..."
}

class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof!"
}

// Interface
interface Drawable {
    fun draw()
    fun clear() { }  // Default implementation
}

// Abstract class
abstract class Shape {
    abstract fun area(): Double
}

Data Classes

// Data class (equals, hashCode, toString, copy auto-generated)
data class Person(
    val name: String,
    val age: Int
)

// Usage
val person = Person("John", 30)
val (name, age) = person  // Destructuring
val older = person.copy(age = 31)

Sealed Classes & Enums

// Sealed class (restricted inheritance)
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

// Exhaustive when
fun handle(result: Result) = when (result) {
    is Result.Success -> println(result.data)
    is Result.Error -> println(result.message)
    Result.Loading -> println("Loading...")
}

// Enum class
enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

Object & Companion

// Singleton
object Database {
    fun connect() { }
}

// Companion object (static-like)
class MyClass {
    companion object {
        const val TAG = "MyClass"
        fun create(): MyClass = MyClass()
    }
}
MyClass.TAG
MyClass.create()

// Anonymous object
val listener = object : ClickListener {
    override fun onClick() { }
}

Collection Operations

// Transformations
list.map { it * 2 }
list.mapNotNull { it.toIntOrNull() }
list.flatMap { listOf(it, it + 1) }

// Filtering
list.filter { it > 0 }
list.filterNot { it > 0 }
list.filterNotNull()
list.take(3)
list.drop(2)
list.distinct()

// Aggregation
list.sum()
list.average()
list.count { it > 0 }
list.maxOrNull()
list.minOrNull()
list.reduce { acc, i -> acc + i }
list.fold(0) { acc, i -> acc + i }

// Finding
list.find { it > 5 }
list.firstOrNull { it > 5 }
list.any { it > 5 }
list.all { it > 0 }
list.none { it < 0 }

// Sorting
list.sorted()
list.sortedDescending()
list.sortedBy { it.name }

// Grouping
list.groupBy { it.category }
list.partition { it > 0 }  // Pair of lists
list.chunked(3)

// Association
list.associateBy { it.id }
list.associate { it.id to it.name }

Scope Functions

// let - null check, transform
val length = name?.let { it.length }

// run - execute block, return result
val result = service.run {
    connect()
    query()
}

// with - call methods on object
val result = with(builder) {
    setName("John")
    setAge(30)
    build()
}

// apply - configure object, return object
val person = Person().apply {
    name = "John"
    age = 30
}

// also - side effects, return object
val numbers = mutableListOf(1, 2, 3).also {
    println("Created list: $it")
}

Coroutines

// Basic coroutine
suspend fun fetchData(): String {
    delay(1000)
    return "Data"
}

// Launch - fire and forget
GlobalScope.launch {
    val data = fetchData()
    println(data)
}

// Async - get result
val deferred = GlobalScope.async {
    fetchData()
}
val result = deferred.await()

// Structured concurrency
coroutineScope {
    val data1 = async { fetchData1() }
    val data2 = async { fetchData2() }
    println("${data1.await()} ${data2.await()}")
}

// Flow
fun numbers(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

numbers()
    .filter { it > 1 }
    .map { it * 2 }
    .collect { println(it) }

Useful Snippets

// Safe type cast with smart cast
if (obj is String) {
    println(obj.length)  // Smart cast to String
}

// Require and check
fun process(value: Int) {
    require(value > 0) { "Value must be positive" }
    check(isInitialized) { "Not initialized" }
}

// Use - auto-close resources
File("file.txt").bufferedReader().use { reader ->
    reader.readLine()
}

// Read file
val content = File("file.txt").readText()
val lines = File("file.txt").readLines()

// Measure time
val time = measureTimeMillis {
    // code to measure
}

// Repeat
repeat(5) { index ->
    println("Iteration $index")
}

// TODO with exception
fun notImplemented(): Nothing = TODO("Not implemented yet")