Skip to content

Kotlin Cheatsheet

Kotlin - Modern Programming Language for Android and Beyond

Kotlin is a modern, concise, and safe programming language that runs on the JVM and is fully interoperable with Java. It's Google's preferred language for Android development and is also used for server-side, web, and multiplatform development.

Table of Contents

Installation

Android Studio Setup

# Download Android Studio from developer.android.com
# Kotlin is included by default

# Create new Kotlin project
# File > New > New Project > Select Kotlin

# Verify Kotlin version in build.gradle
kotlin_version = '1.8.20'

IntelliJ IDEA Setup

# Download IntelliJ IDEA
# Kotlin plugin is bundled

# Create new Kotlin project
# File > New > Project > Kotlin

# Command line compilation
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

Command Line Setup

# Install Kotlin compiler
# macOS with Homebrew
brew install kotlin

# Linux/Windows - download from kotlinlang.org
# Extract and add to PATH

# Verify installation
kotlin -version
kotlinc -version

# REPL (Read-Eval-Print Loop)
kotlinc-jvm

# Compile and run
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

# Direct execution
kotlin hello.kt

Basic Syntax

Hello World

// Simple main function
fun main() {
    println("Hello, World!")
}

// Main with arguments
fun main(args: Array<String>) {
    println("Hello, ${args.getOrNull(0) ?: "World"}!")
}

// Top-level function
fun greet(name: String) {
    println("Hello, $name!")
}

fun main() {
    greet("Kotlin")
}

// Comments
// This is a single-line comment

/*
 * This is a multi-line comment
 * that spans multiple lines
 */

/**
 * This is a documentation comment
 * @param name The name to greet
 * @return A greeting string
 */
fun createGreeting(name: String): String {
    return "Hello, $name!"
}

Package and Imports

// Package declaration
package com.example.myapp

// Imports
import kotlin.math.*
import java.util.Date
import android.content.Context
import com.example.utils.Helper as UtilHelper

// Import all from package
import kotlin.collections.*

// Import specific function
import kotlin.math.sqrt

class MyClass {
    fun useImports() {
        val result = sqrt(16.0)
        val date = Date()
        val helper = UtilHelper()
    }
}

Variables and Constants

Variable Declaration

// Mutable variables (var)
var name = "John"           // Type inferred as String
var age: Int = 25          // Explicit type
var height = 5.9           // Type inferred as Double

// Immutable variables (val)
val pi = 3.14159           // Type inferred as Double
val maxUsers: Int = 100    // Explicit type
val appName = "MyApp"      // Type inferred as String

// Late initialization
lateinit var database: Database
val lazyValue: String by lazy {
    "Computed only when accessed"
}

// Nullable variables
var nullableName: String? = null
var nonNullName: String = "John"

// Multiple variable declaration
val (x, y, z) = Triple(1, 2, 3)
val (first, second) = Pair("Hello", "World")

Type Inference and Explicit Types

// Type inference
val number = 42                    // Int
val decimal = 3.14                 // Double
val text = "Hello"                 // String
val flag = true                    // Boolean

// Explicit types
val explicitInt: Int = 42
val explicitDouble: Double = 3.14
val explicitString: String = "Hello"
val explicitBoolean: Boolean = true

// Collections with type inference
val numbers = listOf(1, 2, 3, 4, 5)           // List<Int>
val names = mutableListOf("Alice", "Bob")      // MutableList<String>
val ages = mapOf("Alice" to 30, "Bob" to 25)   // Map<String, Int>

// Explicit collection types
val explicitList: List<String> = listOf("a", "b", "c")
val explicitMap: Map<String, Int> = mapOf("key" to 1)

Data Types

Basic Types

// Numbers
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2147483647
val long: Long = 9223372036854775807L
val float: Float = 3.14f
val double: Double = 3.141592653589793

// Characters and Strings
val char: Char = 'A'
val string: String = "Hello, Kotlin!"

// Booleans
val isTrue: Boolean = true
val isFalse: Boolean = false

// Arrays
val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
val stringArray: Array<String> = arrayOf("a", "b", "c")
val nullableArray: Array<String?> = arrayOfNulls(5)

// Type conversion
val intValue = 42
val longValue = intValue.toLong()
val doubleValue = intValue.toDouble()
val stringValue = intValue.toString()

// Number literals
val binary = 0b1010        // Binary
val hex = 0xFF             // Hexadecimal
val scientific = 1.23e10   // Scientific notation

String Operations

// String templates
val name = "Alice"
val age = 30
val message = "My name is $name and I'm $age years old"
val calculation = "2 + 3 = ${2 + 3}"

// Multi-line strings
val poem = """
    Roses are red,
    Violets are blue,
    Kotlin is awesome,
    And so are you!
""".trimIndent()

// Raw strings
val regex = """[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"""

// String operations
val text = "Hello, Kotlin!"
println(text.length)                    // 14
println(text.uppercase())               // HELLO, KOTLIN!
println(text.lowercase())               // hello, kotlin!
println(text.substring(0, 5))          // Hello
println(text.contains("Kotlin"))       // true
println(text.startsWith("Hello"))      // true
println(text.endsWith("!"))            // true
println(text.replace("Kotlin", "World")) // Hello, World!

// String comparison
val str1 = "hello"
val str2 = "HELLO"
println(str1 == str2)                   // false
println(str1.equals(str2, ignoreCase = true)) // true

Collections Overview

// Lists
val readOnlyList = listOf("apple", "banana", "cherry")
val mutableList = mutableListOf("apple", "banana")
mutableList.add("cherry")

// Sets
val readOnlySet = setOf(1, 2, 3, 3, 4)  // {1, 2, 3, 4}
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)

// Maps
val readOnlyMap = mapOf("a" to 1, "b" to 2, "c" to 3)
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3

// Ranges
val range1 = 1..10                      // 1 to 10 (inclusive)
val range2 = 1 until 10                 // 1 to 9 (exclusive)
val range3 = 10 downTo 1                // 10 to 1 (descending)
val range4 = 1..10 step 2               // 1, 3, 5, 7, 9

// Checking membership
println(5 in range1)                    // true
println('x' in 'a'..'z')               // true

Control Flow

Conditional Statements

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

// if-else chain
val result = if (score >= 90) {
    "A"
} else if (score >= 80) {
    "B"
} else if (score >= 70) {
    "C"
} else {
    "F"
}

// when expression (similar to switch)
val dayOfWeek = when (day) {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6, 7 -> "Weekend"
    else -> "Invalid day"
}

// when with ranges
val grade = when (score) {
    in 90..100 -> "A"
    in 80..89 -> "B"
    in 70..79 -> "C"
    in 60..69 -> "D"
    else -> "F"
}

// when with type checking
fun describe(obj: Any): String = when (obj) {
    1 -> "One"
    "Hello" -> "Greeting"
    is Long -> "Long"
    !is String -> "Not a string"
    else -> "Unknown"
}

// when without argument
val temperature = 25
val description = when {
    temperature < 0 -> "Freezing"
    temperature < 20 -> "Cold"
    temperature < 30 -> "Warm"
    else -> "Hot"
}

Loops

// for loop with range
for (i in 1..5) {
    println(i)
}

// for loop with until
for (i in 1 until 5) {
    println(i) // 1, 2, 3, 4
}

// for loop with step
for (i in 1..10 step 2) {
    println(i) // 1, 3, 5, 7, 9
}

// for loop with downTo
for (i in 10 downTo 1 step 2) {
    println(i) // 10, 8, 6, 4, 2
}

// for loop with collections
val fruits = listOf("apple", "banana", "cherry")
for (fruit in fruits) {
    println(fruit)
}

// for loop with indices
for (i in fruits.indices) {
    println("$i: ${fruits[i]}")
}

// for loop with withIndex
for ((index, value) in fruits.withIndex()) {
    println("$index: $value")
}

// while loop
var count = 0
while (count < 5) {
    println("Count: $count")
    count++
}

// do-while loop
var number = 0
do {
    println("Number: $number")
    number++
} while (number < 3)

// Loop control
for (i in 1..10) {
    if (i == 3) continue    // Skip iteration
    if (i == 8) break       // Exit loop
    println(i)
}

// Labeled breaks and continues
outer@ for (i in 1..3) {
    inner@ for (j in 1..3) {
        if (i == 2 && j == 2) break@outer
        println("$i, $j")
    }
}

Functions

Basic Functions

// Simple function
fun greet() {
    println("Hello!")
}

// Function with parameters
fun greet(name: String) {
    println("Hello, $name!")
}

// Function with return type
fun add(a: Int, b: Int): Int {
    return a + b
}

// Single-expression function
fun multiply(a: Int, b: Int): Int = a * b

// Function with default parameters
fun greet(name: String, greeting: String = "Hello") {
    println("$greeting, $name!")
}

// Function with named arguments
fun createUser(name: String, age: Int, email: String) {
    // Implementation
}

// Calling with named arguments
createUser(name = "Alice", email = "alice@example.com", age = 30)

// Vararg parameters
fun sum(vararg numbers: Int): Int {
    return numbers.sum()
}

val result = sum(1, 2, 3, 4, 5)

// Spreading arrays
val array = intArrayOf(1, 2, 3)
val total = sum(*array)

Function Types and Higher-Order Functions

// Function type
val operation: (Int, Int) -> Int = { a, b -> a + b }

// Higher-order function
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

val result = calculate(5, 3) { x, y -> x * y }

// Function returning function
fun getOperation(type: String): (Int, Int) -> Int {
    return when (type) {
        "add" -> { a, b -> a + b }
        "multiply" -> { a, b -> a * b }
        else -> { a, b -> 0 }
    }
}

val addFunction = getOperation("add")
val sum = addFunction(5, 3)

// Lambda expressions
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
val sum = numbers.reduce { acc, n -> acc + n }

// Lambda with multiple parameters
val pairs = listOf(Pair(1, 2), Pair(3, 4), Pair(5, 6))
val sums = pairs.map { (first, second) -> first + second }

// Trailing lambda syntax
numbers.forEach { number ->
    println(number)
}

// it parameter (single parameter lambda)
numbers.forEach {
    println(it)
}

Inline Functions

// Inline function
inline fun measureTime(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    return end - start
}

// Usage
val time = measureTime {
    // Some operation
    Thread.sleep(1000)
}

// noinline parameter
inline fun processData(
    data: List<String>,
    noinline logger: (String) -> Unit,
    processor: (String) -> String
) {
    data.forEach { item ->
        logger("Processing: $item")
        processor(item)
    }
}

// crossinline parameter
inline fun runAsync(crossinline block: () -> Unit) {
    Thread {
        block()
    }.start()
}

Classes and Objects

Basic Classes

// Simple class
class Person {
    var name: String = ""
    var age: Int = 0

    fun introduce() {
        println("Hi, I'm $name and I'm $age years old")
    }
}

// Class with primary constructor
class Person(val name: String, var age: Int) {
    fun introduce() {
        println("Hi, I'm $name and I'm $age years old")
    }
}

// Class with init block
class Person(name: String, age: Int) {
    val name: String
    var age: Int

    init {
        this.name = name.uppercase()
        this.age = if (age >= 0) age else 0
        println("Person created: ${this.name}")
    }
}

// Secondary constructors
class Person(val name: String) {
    var age: Int = 0
    var email: String = ""

    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }

    constructor(name: String, age: Int, email: String) : this(name, age) {
        this.email = email
    }
}

// Usage
val person1 = Person("Alice")
val person2 = Person("Bob", 30)
val person3 = Person("Charlie", 25, "charlie@example.com")

Properties

class Rectangle(val width: Double, val height: Double) {
    // Computed property
    val area: Double
        get() = width * height

    // Property with custom getter and setter
    var isSquare: Boolean
        get() = width == height
        set(value) {
            if (value) {
                // Make it a square by setting height to width
                // Note: This is just an example, height is val so this won't work
                println("Cannot make rectangle a square - height is immutable")
            }
        }

    // Property with backing field
    var color: String = "white"
        set(value) {
            field = value.lowercase()
        }

    // Late-initialized property
    lateinit var description: String

    fun initializeDescription() {
        description = "A $color rectangle with area $area"
    }
}

// Property delegation
class User {
    var name: String by Delegates.observable("") { prop, old, new ->
        println("$old -> $new")
    }

    val lazyValue: String by lazy {
        println("Computed!")
        "Hello"
    }
}

Visibility Modifiers

// Visibility modifiers
class Example {
    private val privateProperty = "Only visible within this class"
    protected val protectedProperty = "Visible in this class and subclasses"
    internal val internalProperty = "Visible within the same module"
    public val publicProperty = "Visible everywhere" // public is default

    private fun privateFunction() { }
    protected fun protectedFunction() { }
    internal fun internalFunction() { }
    fun publicFunction() { } // public is default
}

// Top-level declarations
private fun topLevelPrivate() { } // Visible within the same file
internal fun topLevelInternal() { } // Visible within the same module
fun topLevelPublic() { } // Visible everywhere

Object Declarations and Expressions

// Object declaration (Singleton)
object DatabaseManager {
    fun connect() {
        println("Connecting to database...")
    }

    fun disconnect() {
        println("Disconnecting from database...")
    }
}

// Usage
DatabaseManager.connect()

// Object expression (Anonymous object)
val clickListener = object : View.OnClickListener {
    override fun onClick(v: View?) {
        println("Button clicked!")
    }
}

// Object expression with multiple interfaces
val handler = object : Runnable, Serializable {
    override fun run() {
        println("Running...")
    }
}

// Companion object
class MyClass {
    companion object {
        const val CONSTANT = "Hello"

        fun create(): MyClass {
            return MyClass()
        }
    }
}

// Usage
val instance = MyClass.create()
println(MyClass.CONSTANT)

Inheritance

Basic Inheritance

// Base class (open for inheritance)
open class Animal(val name: String) {
    open fun makeSound() {
        println("$name makes a sound")
    }

    open val species: String = "Unknown"
}

// Derived class
class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("$name barks")
    }

    override val species: String = "Canis lupus"

    fun fetch() {
        println("$name fetches the ball")
    }
}

class Cat(name: String, val breed: String) : Animal(name) {
    override fun makeSound() {
        println("$name meows")
    }

    override val species: String = "Felis catus"

    fun purr() {
        println("$name purrs")
    }
}

// Usage
val dog = Dog("Buddy")
val cat = Cat("Whiskers", "Persian")

dog.makeSound() // Buddy barks
cat.makeSound() // Whiskers meows

Abstract Classes

// Abstract class
abstract class Shape {
    abstract val area: Double
    abstract val perimeter: Double

    abstract fun draw()

    // Concrete method
    fun describe() {
        println("This shape has area $area and perimeter $perimeter")
    }
}

class Circle(val radius: Double) : Shape() {
    override val area: Double
        get() = Math.PI * radius * radius

    override val perimeter: Double
        get() = 2 * Math.PI * radius

    override fun draw() {
        println("Drawing a circle with radius $radius")
    }
}

class Rectangle(val width: Double, val height: Double) : Shape() {
    override val area: Double
        get() = width * height

    override val perimeter: Double
        get() = 2 * (width + height)

    override fun draw() {
        println("Drawing a rectangle ${width}x${height}")
    }
}

Calling Superclass Implementation

open class Vehicle(val brand: String) {
    open fun start() {
        println("$brand vehicle is starting...")
    }

    open fun stop() {
        println("$brand vehicle is stopping...")
    }
}

class Car(brand: String, val model: String) : Vehicle(brand) {
    override fun start() {
        super.start() // Call superclass implementation
        println("$brand $model engine is running")
    }

    override fun stop() {
        println("$brand $model is parking")
        super.stop() // Call superclass implementation
    }
}

// Usage
val car = Car("Toyota", "Camry")
car.start()
// Output:
// Toyota vehicle is starting...
// Toyota Camry engine is running

Interfaces

Basic Interfaces

// Interface definition
interface Drawable {
    fun draw()
    fun getArea(): Double
}

interface Clickable {
    fun click()
    fun showTooltip() = println("Tooltip") // Default implementation
}

// Implementing interfaces
class Button : Drawable, Clickable {
    override fun draw() {
        println("Drawing a button")
    }

    override fun getArea(): Double {
        return 100.0 // Example area
    }

    override fun click() {
        println("Button clicked!")
    }

    // showTooltip() is inherited from Clickable with default implementation
}

// Interface with properties
interface Named {
    val name: String
    val displayName: String
        get() = name.uppercase() // Property with default getter
}

class User(override val name: String) : Named {
    // displayName is automatically available
}

Interface Conflicts

interface A {
    fun foo() {
        println("A")
    }

    fun bar()
}

interface B {
    fun foo() {
        println("B")
    }

    fun bar() {
        println("bar from B")
    }
}

class C : A, B {
    override fun foo() {
        super<A>.foo() // Call A's implementation
        super<B>.foo() // Call B's implementation
    }

    override fun bar() {
        super<B>.bar() // Call B's implementation
    }
}

Functional Interfaces (SAM)

// Functional interface
fun interface StringProcessor {
    fun process(input: String): String
}

// Usage with lambda
val processor = StringProcessor { input ->
    input.uppercase()
}

// Or as a function parameter
fun processText(text: String, processor: StringProcessor): String {
    return processor.process(text)
}

val result = processText("hello") { it.uppercase() }

// Built-in functional interfaces
val runnable = Runnable {
    println("Running...")
}

val comparator = Comparator<String> { a, b ->
    a.length.compareTo(b.length)
}

Data Classes

Basic Data Classes

// Data class
data class User(val name: String, val age: Int, val email: String)

// Automatically generated methods:
// - equals() and hashCode()
// - toString()
// - copy()
// - componentN() functions for destructuring

val user1 = User("Alice", 30, "alice@example.com")
val user2 = User("Alice", 30, "alice@example.com")

println(user1 == user2) // true (structural equality)
println(user1) // User(name=Alice, age=30, email=alice@example.com)

// Copy with modifications
val user3 = user1.copy(age = 31)
println(user3) // User(name=Alice, age=31, email=alice@example.com)

// Destructuring
val (name, age, email) = user1
println("Name: $name, Age: $age, Email: $email")

Data Class with Custom Behavior

data class Point(val x: Double, val y: Double) {
    // Additional methods
    fun distanceFromOrigin(): Double {
        return kotlin.math.sqrt(x * x + y * y)
    }

    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

val point1 = Point(3.0, 4.0)
val point2 = Point(1.0, 2.0)

println(point1.distanceFromOrigin()) // 5.0
val sum = point1 + point2
println(sum) // Point(x=4.0, y=6.0)

// Data class with validation
data class Email(val address: String) {
    init {
        require(address.contains("@")) { "Invalid email address" }
    }
}

// Data class with computed properties
data class Rectangle(val width: Double, val height: Double) {
    val area: Double
        get() = width * height

    val perimeter: Double
        get() = 2 * (width + height)
}

Data Class Collections

data class Product(val id: Int, val name: String, val price: Double)

val products = listOf(
    Product(1, "Laptop", 999.99),
    Product(2, "Mouse", 29.99),
    Product(3, "Keyboard", 79.99)
)

// Using data classes in collections
val expensiveProducts = products.filter { it.price > 50.0 }
val productNames = products.map { it.name }
val totalPrice = products.sumOf { it.price }

// Grouping
val productsByPriceRange = products.groupBy { product ->
    when {
        product.price < 50 -> "Cheap"
        product.price < 100 -> "Medium"
        else -> "Expensive"
    }
}

// Finding
val laptop = products.find { it.name == "Laptop" }
val hasExpensiveItem = products.any { it.price > 500 }

Sealed Classes

Basic Sealed Classes

// Sealed class for representing states
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

// Usage with when expression
fun handleResult(result: Result<String>) {
    when (result) {
        is Result.Success -> println("Data: ${result.data}")
        is Result.Error -> println("Error: ${result.exception.message}")
        is Result.Loading -> println("Loading...")
        // No else clause needed - compiler knows all cases are covered
    }
}

// Example usage
val successResult = Result.Success("Hello, World!")
val errorResult = Result.Error(Exception("Network error"))
val loadingResult = Result.Loading

handleResult(successResult)
handleResult(errorResult)
handleResult(loadingResult)

Sealed Classes for Navigation

sealed class Screen(val route: String) {
    object Home : Screen("home")
    object Profile : Screen("profile")
    data class UserDetail(val userId: String) : Screen("user/$userId")
    data class ProductDetail(val productId: String) : Screen("product/$productId")
}

fun navigate(screen: Screen) {
    when (screen) {
        is Screen.Home -> println("Navigating to home")
        is Screen.Profile -> println("Navigating to profile")
        is Screen.UserDetail -> println("Navigating to user ${screen.userId}")
        is Screen.ProductDetail -> println("Navigating to product ${screen.productId}")
    }
}

// Usage
navigate(Screen.Home)
navigate(Screen.UserDetail("123"))
navigate(Screen.ProductDetail("abc"))

Sealed Interfaces

sealed interface NetworkResult
data class Success(val data: String) : NetworkResult
data class Error(val code: Int, val message: String) : NetworkResult
object Loading : NetworkResult

fun processNetworkResult(result: NetworkResult) {
    when (result) {
        is Success -> println("Success: ${result.data}")
        is Error -> println("Error ${result.code}: ${result.message}")
        is Loading -> println("Loading...")
    }
}

Generics

Basic Generics

// Generic class
class Box<T>(val value: T) {
    fun get(): T = value

    fun <U> transform(transformer: (T) -> U): Box<U> {
        return Box(transformer(value))
    }
}

val intBox = Box(42)
val stringBox = Box("Hello")

val doubledBox = intBox.transform { it * 2 }
val upperBox = stringBox.transform { it.uppercase() }

// Generic function
fun <T> swap(a: T, b: T): Pair<T, T> {
    return Pair(b, a)
}

val swapped = swap("hello", "world")
val swappedNumbers = swap(1, 2)

// Multiple type parameters
class Pair<A, B>(val first: A, val second: B) {
    fun <C> mapFirst(transform: (A) -> C): Pair<C, B> {
        return Pair(transform(first), second)
    }

    fun <C> mapSecond(transform: (B) -> C): Pair<A, C> {
        return Pair(first, transform(second))
    }
}

Type Constraints

// Upper bound constraint
fun <T : Number> sum(a: T, b: T): Double {
    return a.toDouble() + b.toDouble()
}

val intSum = sum(1, 2)
val doubleSum = sum(1.5, 2.5)
// val stringSum = sum("a", "b") // Compilation error

// Multiple constraints
interface Drawable {
    fun draw()
}

interface Clickable {
    fun click()
}

fun <T> handleUI(element: T) where T : Drawable, T : Clickable {
    element.draw()
    element.click()
}

// Constraint with class and interface
open class View
interface OnClickListener

fun <T> setupView(view: T) where T : View, T : OnClickListener {
    // T must extend View and implement OnClickListener
}

Variance

// Covariance (out)
interface Producer<out T> {
    fun produce(): T
}

class StringProducer : Producer<String> {
    override fun produce(): String = "Hello"
}

// Can assign Producer<String> to Producer<Any>
val stringProducer: Producer<String> = StringProducer()
val anyProducer: Producer<Any> = stringProducer

// Contravariance (in)
interface Consumer<in T> {
    fun consume(item: T)
}

class AnyConsumer : Consumer<Any> {
    override fun consume(item: Any) {
        println("Consuming: $item")
    }
}

// Can assign Consumer<Any> to Consumer<String>
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer

// Invariance (default)
class MutableBox<T>(var value: T) {
    fun get(): T = value
    fun set(value: T) {
        this.value = value
    }
}

// Star projection
fun printList(list: List<*>) {
    for (item in list) {
        println(item)
    }
}

Reified Type Parameters

// Reified type parameters (only in inline functions)
inline fun <reified T> isInstance(value: Any): Boolean {
    return value is T
}

val result1 = isInstance<String>("hello") // true
val result2 = isInstance<Int>("hello")    // false

// Reified with class access
inline fun <reified T> createInstance(): T? {
    return try {
        T::class.java.getDeclaredConstructor().newInstance()
    } catch (e: Exception) {
        null
    }
}

// Reified with JSON parsing (example)
inline fun <reified T> parseJson(json: String): T {
    // Implementation would use T::class.java
    TODO("Implementation depends on JSON library")
}

Collections

Lists

// Immutable list
val readOnlyList = listOf("apple", "banana", "cherry")
val emptyList = emptyList<String>()

// Mutable list
val mutableList = mutableListOf("apple", "banana")
mutableList.add("cherry")
mutableList.addAll(listOf("date", "elderberry"))
mutableList.remove("banana")
mutableList.removeAt(0)

// List operations
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
val sum = numbers.sum()
val max = numbers.maxOrNull()
val sorted = numbers.sortedDescending()

// List access
val first = numbers.first()
val last = numbers.last()
val secondElement = numbers[1]
val safeAccess = numbers.getOrNull(10) // null if index out of bounds

// List slicing
val sublist = numbers.subList(1, 4) // [2, 3, 4]
val take = numbers.take(3) // [1, 2, 3]
val drop = numbers.drop(2) // [3, 4, 5]

// List transformation
val fruits = listOf("apple", "banana", "cherry")
val lengths = fruits.map { it.length }
val uppercase = fruits.map { it.uppercase() }
val indexed = fruits.mapIndexed { index, fruit -> "$index: $fruit" }

// Flattening
val nestedLists = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
val flattened = nestedLists.flatten() // [1, 2, 3, 4, 5, 6]

val words = listOf("hello", "world")
val characters = words.flatMap { it.toList() } // [h, e, l, l, o, w, o, r, l, d]

Sets

// Immutable set
val readOnlySet = setOf(1, 2, 3, 3, 4) // {1, 2, 3, 4}
val emptySet = emptySet<Int>()

// Mutable set
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
mutableSet.addAll(setOf(5, 6))
mutableSet.remove(2)

// Set operations
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)

val union = set1 union set2        // {1, 2, 3, 4, 5, 6}
val intersection = set1 intersect set2  // {3, 4}
val difference = set1 subtract set2     // {1, 2}

// Set membership
val contains = 3 in set1           // true
val containsAll = set1.containsAll(setOf(1, 2)) // true

// Converting between collections
val listToSet = listOf(1, 2, 2, 3).toSet() // {1, 2, 3}
val setToList = setOf(1, 2, 3).toList()    // [1, 2, 3]

Maps

// Immutable map
val readOnlyMap = mapOf("a" to 1, "b" to 2, "c" to 3)
val emptyMap = emptyMap<String, Int>()

// Mutable map
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3
mutableMap.put("d", 4)
mutableMap.putAll(mapOf("e" to 5, "f" to 6))
mutableMap.remove("b")

// Map access
val value = readOnlyMap["a"]           // 1 (nullable)
val safeValue = readOnlyMap.getValue("a") // 1 (throws if not found)
val defaultValue = readOnlyMap.getOrDefault("z", 0) // 0
val computedValue = mutableMap.getOrPut("g") { 7 }

// Map operations
val ages = mapOf("Alice" to 30, "Bob" to 25, "Charlie" to 35)
val names = ages.keys
val ageValues = ages.values
val entries = ages.entries

// Map transformation
val upperNames = ages.mapKeys { it.key.uppercase() }
val ageInMonths = ages.mapValues { it.value * 12 }
val filtered = ages.filter { it.value >= 30 }

// Map iteration
for ((name, age) in ages) {
    println("$name is $age years old")
}

ages.forEach { (name, age) ->
    println("$name: $age")
}

Collection Operations

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// Filtering
val evens = numbers.filter { it % 2 == 0 }
val odds = numbers.filterNot { it % 2 == 0 }
val nonNulls = listOf(1, null, 2, null, 3).filterNotNull()

// Mapping
val doubled = numbers.map { it * 2 }
val strings = numbers.map { "Number: $it" }

// Reducing
val sum = numbers.reduce { acc, n -> acc + n }
val product = numbers.fold(1) { acc, n -> acc * n }

// Grouping
val words = listOf("apple", "banana", "cherry", "apricot", "blueberry")
val groupedByFirstLetter = words.groupBy { it.first() }
val groupedByLength = words.groupBy { it.length }

// Partitioning
val (evens2, odds2) = numbers.partition { it % 2 == 0 }

// Finding
val firstEven = numbers.find { it % 2 == 0 }
val lastOdd = numbers.findLast { it % 2 == 1 }

// Checking conditions
val allPositive = numbers.all { it > 0 }
val anyEven = numbers.any { it % 2 == 0 }
val noneNegative = numbers.none { it < 0 }

// Sorting
val sorted = numbers.sorted()
val sortedDesc = numbers.sortedDescending()
val sortedByLength = words.sortedBy { it.length }
val sortedByLengthDesc = words.sortedByDescending { it.length }

// Distinct
val duplicates = listOf(1, 2, 2, 3, 3, 3, 4)
val unique = duplicates.distinct()
val uniqueByLength = words.distinctBy { it.length }

// Zipping
val letters = listOf("a", "b", "c")
val zipped = numbers.zip(letters) // [(1, a), (2, b), (3, c)]
val zippedWithTransform = numbers.zip(letters) { num, letter -> "$num$letter" }

Null Safety

Nullable Types

// Nullable vs non-nullable types
var nonNullString: String = "Hello"
var nullableString: String? = null

// nonNullString = null // Compilation error
nullableString = "World"
nullableString = null // OK

// Safe call operator
val length = nullableString?.length
val uppercase = nullableString?.uppercase()

// Chaining safe calls
class Person(val name: String?)
class Company(val ceo: Person?)

val company: Company? = Company(Person("John"))
val ceoNameLength = company?.ceo?.name?.length

// Elvis operator
val name = nullableString ?: "Default Name"
val length2 = nullableString?.length ?: 0

// Not-null assertion operator (use with caution)
val definitelyNotNull = nullableString!!
val length3 = nullableString!!.length

// Safe cast
val stringValue: Any = "Hello"
val safeString = stringValue as? String // null if cast fails
val unsafeString = stringValue as String // throws exception if cast fails

Null Checks

fun processString(str: String?) {
    // Explicit null check
    if (str != null) {
        println(str.length) // Smart cast to non-null
    }

    // Safe call with let
    str?.let { nonNullStr ->
        println("Processing: $nonNullStr")
        println("Length: ${nonNullStr.length}")
    }

    // Elvis operator with return
    val length = str?.length ?: return
    println("String length: $length")

    // Elvis operator with throw
    val upperCase = str?.uppercase() ?: throw IllegalArgumentException("String cannot be null")
}

// Multiple null checks
fun processPersonInfo(person: Person?) {
    person?.let { p ->
        p.name?.let { name ->
            println("Person name: $name")
        }
    }

    // Or using safe calls
    person?.name?.let { name ->
        println("Person name: $name")
    }
}

Collections and Null Safety

// Nullable collections vs collections of nullables
val nullableList: List<String>? = null
val listOfNullables: List<String?> = listOf("hello", null, "world")
val nullableListOfNullables: List<String?>? = null

// Safe operations on nullable collections
nullableList?.forEach { item ->
    println(item)
}

val size = nullableList?.size ?: 0

// Filtering out nulls
val nonNullItems = listOfNullables.filterNotNull()
println(nonNullItems) // [hello, world]

// Map with null safety
val lengths = listOfNullables.mapNotNull { it?.length }
println(lengths) // [5, 5]

// Safe indexing
val firstItem = nullableList?.getOrNull(0)
val safeAccess = listOfNullables.getOrNull(10) // null

Platform Types

// When interacting with Java code, types are platform types (T!)
// These can be treated as nullable or non-nullable

// Java method: public String getName() { return name; }
// In Kotlin, this becomes: fun getName(): String!

// You can treat it as nullable
val javaName = javaObject.getName()
val safeName = javaName?.uppercase()

// Or as non-nullable (your responsibility to ensure it's not null)
val definitelyName: String = javaObject.getName()
val upperName = definitelyName.uppercase()

// Best practice: explicitly declare nullability
val explicitlyNullable: String? = javaObject.getName()
val explicitlyNonNull: String = javaObject.getName() ?: ""

Extension Functions

Basic Extension Functions

// Extension function for String
fun String.isPalindrome(): Boolean {
    val cleaned = this.lowercase().replace(Regex("[^a-z0-9]"), "")
    return cleaned == cleaned.reversed()
}

// Usage
val text = "A man a plan a canal Panama"
println(text.isPalindrome()) // true

// Extension function with parameters
fun String.truncate(maxLength: Int, suffix: String = "..."): String {
    return if (this.length <= maxLength) {
        this
    } else {
        this.take(maxLength - suffix.length) + suffix
    }
}

val longText = "This is a very long text that needs to be truncated"
println(longText.truncate(20)) // This is a very lo...

// Extension function for collections
fun <T> List<T>.secondOrNull(): T? {
    return if (this.size >= 2) this[1] else null
}

val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.secondOrNull()) // 2

val emptyList = emptyList<Int>()
println(emptyList.secondOrNull()) // null

Extension Properties

// Extension property for String
val String.lastIndex: Int
    get() = this.length - 1

val text = "Hello"
println(text.lastIndex) // 4

// Extension property for collections
val <T> List<T>.penultimate: T?
    get() = if (this.size >= 2) this[this.size - 2] else null

val fruits = listOf("apple", "banana", "cherry")
println(fruits.penultimate) // banana

// Extension property with backing field (not allowed)
// var String.customProperty: String = "" // Compilation error

// But you can use other mechanisms
private val stringExtras = mutableMapOf<String, String>()

var String.extra: String
    get() = stringExtras[this] ?: ""
    set(value) {
        stringExtras[this] = value
    }

Extension Functions for Custom Classes

data class Point(val x: Double, val y: Double)

// Extension function for custom class
fun Point.distanceTo(other: Point): Double {
    val dx = this.x - other.x
    val dy = this.y - other.y
    return kotlin.math.sqrt(dx * dx + dy * dy)
}

// Extension operator
operator fun Point.plus(other: Point): Point {
    return Point(this.x + other.x, this.y + other.y)
}

operator fun Point.times(scalar: Double): Point {
    return Point(this.x * scalar, this.y * scalar)
}

// Usage
val point1 = Point(1.0, 2.0)
val point2 = Point(4.0, 6.0)

println(point1.distanceTo(point2)) // 5.0
val sum = point1 + point2
val scaled = point1 * 2.0

// Extension function with receiver type parameter
fun <T> T.applyIf(condition: Boolean, block: T.() -> T): T {
    return if (condition) this.block() else this
}

val result = "hello"
    .applyIf(true) { uppercase() }
    .applyIf(false) { reversed() }
println(result) // HELLO

Scope Functions as Extensions

// Custom scope functions
inline fun <T> T.alsoIf(condition: Boolean, block: (T) -> Unit): T {
    if (condition) block(this)
    return this
}

inline fun <T, R> T.letIf(condition: Boolean, block: (T) -> R): R? {
    return if (condition) block(this) else null
}

// Usage
val number = 42
    .alsoIf(true) { println("Number is $it") }
    .alsoIf(false) { println("This won't print") }

val result = "hello"
    .letIf(true) { it.uppercase() }
    ?.let { "Result: $it" }

// Extension for nullable types
fun <T> T?.ifNotNull(block: (T) -> Unit) {
    if (this != null) block(this)
}

val nullableString: String? = "hello"
nullableString.ifNotNull { println("String: $it") }

Higher-Order Functions

Function Types

// Function type declarations
val add: (Int, Int) -> Int = { a, b -> a + b }
val greet: (String) -> Unit = { name -> println("Hello, $name!") }
val isEven: (Int) -> Boolean = { it % 2 == 0 }

// Function type with receiver
val stringBuilder: StringBuilder.() -> Unit = {
    append("Hello, ")
    append("World!")
}

val sb = StringBuilder()
sb.stringBuilder()
println(sb.toString()) // Hello, World!

// Nullable function types
val nullableFunction: ((Int) -> String)? = null
val result = nullableFunction?.invoke(42)

// Function type with multiple parameters
val calculator: (Int, Int, (Int, Int) -> Int) -> Int = { a, b, operation ->
    operation(a, b)
}

val sum = calculator(5, 3) { x, y -> x + y }
val product = calculator(5, 3) { x, y -> x * y }

Higher-Order Function Examples

// Function that takes a function as parameter
fun processNumbers(numbers: List<Int>, processor: (Int) -> Int): List<Int> {
    return numbers.map(processor)
}

val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
val squared = processNumbers(numbers) { it * it }

// Function that returns a function
fun createMultiplier(factor: Int): (Int) -> Int {
    return { number -> number * factor }
}

val doubler = createMultiplier(2)
val tripler = createMultiplier(3)

println(doubler(5)) // 10
println(tripler(5)) // 15

// Function with multiple function parameters
fun combineOperations(
    a: Int,
    b: Int,
    operation1: (Int, Int) -> Int,
    operation2: (Int) -> Int
): Int {
    val intermediate = operation1(a, b)
    return operation2(intermediate)
}

val result = combineOperations(3, 4, { x, y -> x + y }, { it * 2 })
println(result) // 14 (3 + 4 = 7, then 7 * 2 = 14)

Built-in Higher-Order Functions

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// map - transform each element
val doubled = numbers.map { it * 2 }
val strings = numbers.map { "Number: $it" }

// filter - select elements based on condition
val evens = numbers.filter { it % 2 == 0 }
val greaterThanFive = numbers.filter { it > 5 }

// reduce - combine elements into single value
val sum = numbers.reduce { acc, n -> acc + n }
val max = numbers.reduce { acc, n -> if (n > acc) n else acc }

// fold - like reduce but with initial value
val product = numbers.fold(1) { acc, n -> acc * n }
val concatenated = numbers.fold("") { acc, n -> acc + n }

// forEach - perform action on each element
numbers.forEach { println(it) }

// any, all, none - check conditions
val hasEven = numbers.any { it % 2 == 0 }
val allPositive = numbers.all { it > 0 }
val noNegative = numbers.none { it < 0 }

// find - find first element matching condition
val firstEven = numbers.find { it % 2 == 0 }
val firstGreaterThanTen = numbers.find { it > 10 } // null

// partition - split into two lists based on condition
val (evens2, odds) = numbers.partition { it % 2 == 0 }

// groupBy - group elements by key
val words = listOf("apple", "banana", "cherry", "apricot")
val groupedByFirstLetter = words.groupBy { it.first() }

Scope Functions

// let - execute block and return result
val name: String? = "John"
val result = name?.let { nonNullName ->
    "Hello, $nonNullName!"
} ?: "Hello, Guest!"

// run - execute block on object and return result
val message = StringBuilder().run {
    append("Hello, ")
    append("World!")
    toString()
}

// with - execute block on object and return result
val greeting = with(StringBuilder()) {
    append("Hello, ")
    append("World!")
    toString()
}

// apply - execute block on object and return object
val person = Person().apply {
    name = "John"
    age = 30
}

// also - execute block with object and return object
val numbers2 = mutableListOf(1, 2, 3).also { list ->
    println("List size: ${list.size}")
    list.add(4)
}

// takeIf - return object if condition is true, null otherwise
val positiveNumber = (-5).takeIf { it > 0 } // null
val evenNumber = 4.takeIf { it % 2 == 0 } // 4

// takeUnless - return object if condition is false, null otherwise
val notZero = 5.takeUnless { it == 0 } // 5
val notEmpty = "".takeUnless { it.isEmpty() } // null

Coroutines

Basic Coroutines

import kotlinx.coroutines.*

// Basic coroutine
fun main() = runBlocking {
    println("Start")
    delay(1000) // Non-blocking delay
    println("End")
}

// Launch coroutine
fun main() = runBlocking {
    launch {
        delay(1000)
        println("World!")
    }
    println("Hello,")
}

// Async coroutine
fun main() = runBlocking {
    val deferred = async {
        delay(1000)
        "Hello, World!"
    }
    println(deferred.await())
}

// Multiple coroutines
fun main() = runBlocking {
    val job1 = launch {
        repeat(5) { i ->
            println("Job1: $i")
            delay(500)
        }
    }

    val job2 = launch {
        repeat(3) { i ->
            println("Job2: $i")
            delay(800)
        }
    }

    joinAll(job1, job2)
    println("All jobs completed")
}

Suspend Functions

// Suspend function
suspend fun fetchUserData(userId: String): User {
    delay(1000) // Simulate network call
    return User(userId, "John Doe")
}

suspend fun fetchUserPosts(userId: String): List<Post> {
    delay(800) // Simulate network call
    return listOf(Post("Post 1"), Post("Post 2"))
}

// Using suspend functions
fun main() = runBlocking {
    val userId = "123"

    // Sequential execution
    val user = fetchUserData(userId)
    val posts = fetchUserPosts(userId)
    println("User: $user, Posts: $posts")

    // Concurrent execution
    val userDeferred = async { fetchUserData(userId) }
    val postsDeferred = async { fetchUserPosts(userId) }

    val userResult = userDeferred.await()
    val postsResult = postsDeferred.await()
    println("User: $userResult, Posts: $postsResult")
}

// Suspend function with error handling
suspend fun fetchDataWithRetry(url: String, maxRetries: Int = 3): String {
    repeat(maxRetries) { attempt ->
        try {
            return performNetworkCall(url)
        } catch (e: Exception) {
            if (attempt == maxRetries - 1) throw e
            delay(1000 * (attempt + 1)) // Exponential backoff
        }
    }
    throw IllegalStateException("Should not reach here")
}

suspend fun performNetworkCall(url: String): String {
    delay(500) // Simulate network call
    if (Math.random() < 0.7) throw Exception("Network error")
    return "Data from $url"
}

Coroutine Contexts and Dispatchers

import kotlinx.coroutines.*

fun main() = runBlocking {
    // Default dispatcher (optimized for CPU-intensive work)
    launch(Dispatchers.Default) {
        println("Default: ${Thread.currentThread().name}")
    }

    // IO dispatcher (optimized for IO operations)
    launch(Dispatchers.IO) {
        println("IO: ${Thread.currentThread().name}")
    }

    // Main dispatcher (for UI updates, Android/JavaFX)
    // launch(Dispatchers.Main) {
    //     println("Main: ${Thread.currentThread().name}")
    // }

    // Unconfined dispatcher
    launch(Dispatchers.Unconfined) {
        println("Unconfined: ${Thread.currentThread().name}")
    }

    delay(100)
}

// Custom dispatcher
fun main() = runBlocking {
    val customDispatcher = newSingleThreadContext("CustomThread")

    launch(customDispatcher) {
        println("Custom: ${Thread.currentThread().name}")
    }

    customDispatcher.close()
}

// Switching contexts
suspend fun processData() {
    withContext(Dispatchers.IO) {
        // Perform IO operation
        println("IO operation on: ${Thread.currentThread().name}")
    }

    withContext(Dispatchers.Default) {
        // Perform CPU-intensive operation
        println("CPU operation on: ${Thread.currentThread().name}")
    }
}

Coroutine Cancellation and Timeouts

// Cancellation
fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("Job: I'm sleeping $i ...")
            delay(500)
        }
    }

    delay(1300) // Let it run for a bit
    println("Cancelling job...")
    job.cancel() // Cancel the job
    job.join() // Wait for cancellation to complete
    println("Job cancelled")
}

// Cooperative cancellation
suspend fun cooperativeTask() {
    repeat(1000) { i ->
        if (!isActive) return // Check if coroutine is still active
        println("Working $i")
        // Simulate work without delay
        Thread.sleep(100)
    }
}

// Timeout
fun main() = runBlocking {
    try {
        withTimeout(1300) {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out!")
    }
}

// Timeout with null result
fun main() = runBlocking {
    val result = withTimeoutOrNull(1300) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500)
        }
        "Done"
    }
    println("Result: $result") // null if timed out
}

Channels and Flow

import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*

// Channels
fun main() = runBlocking {
    val channel = Channel<Int>()

    launch {
        for (x in 1..5) {
            channel.send(x * x)
        }
        channel.close()
    }

    for (y in channel) {
        println(y)
    }
}

// Producer-consumer with channel
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) {
        send(x * x)
    }
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
}

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

fun main() = runBlocking {
    simpleFlow().collect { value ->
        println(value)
    }
}

// Flow transformations
fun main() = runBlocking {
    (1..5).asFlow()
        .filter { it % 2 == 0 }
        .map { it * it }
        .collect { println(it) }
}

// Flow with exception handling
fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            println("Emitting $i")
            emit(i)
            if (i == 2) throw RuntimeException("Error at $i")
        }
    }
    .catch { e -> emit(-1) } // Handle exception
    .collect { println("Collected $it") }
}

Android Development

Activity Basics

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.viewModels

class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupUI()
        observeViewModel()
    }

    private fun setupUI() {
        // UI setup code
    }

    private fun observeViewModel() {
        viewModel.userData.observe(this) { user ->
            // Update UI with user data
        }
    }

    override fun onStart() {
        super.onStart()
        // Activity is becoming visible
    }

    override fun onResume() {
        super.onResume()
        // Activity is in foreground
    }

    override fun onPause() {
        super.onPause()
        // Activity is partially obscured
    }

    override fun onStop() {
        super.onStop()
        // Activity is no longer visible
    }

    override fun onDestroy() {
        super.onDestroy()
        // Activity is being destroyed
    }
}

View Binding

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupClickListeners()
    }

    private fun setupClickListeners() {
        binding.submitButton.setOnClickListener {
            val text = binding.editText.text.toString()
            binding.textView.text = "Hello, $text!"
        }

        binding.clearButton.setOnClickListener {
            binding.editText.text.clear()
            binding.textView.text = ""
        }
    }
}

ViewModel and LiveData

import androidx.lifecycle.ViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {

    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData

    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean> = _loading

    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error

    fun loadUser(userId: String) {
        viewModelScope.launch {
            _loading.value = true
            try {
                val user = userRepository.getUser(userId)
                _userData.value = user
            } catch (e: Exception) {
                _error.value = e.message
            } finally {
                _loading.value = false
            }
        }
    }

    fun updateUser(user: User) {
        viewModelScope.launch {
            try {
                userRepository.updateUser(user)
                _userData.value = user
            } catch (e: Exception) {
                _error.value = e.message
            }
        }
    }
}

RecyclerView Adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.databinding.ItemUserBinding

class UserAdapter(
    private val onUserClick: (User) -> Unit
) : ListAdapter<User, UserAdapter.UserViewHolder>(UserDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return UserViewHolder(binding)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    inner class UserViewHolder(
        private val binding: ItemUserBinding
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(user: User) {
            binding.nameTextView.text = user.name
            binding.emailTextView.text = user.email
            binding.ageTextView.text = user.age.toString()

            binding.root.setOnClickListener {
                onUserClick(user)
            }
        }
    }

    class UserDiffCallback : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
}

Repository Pattern

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {

    suspend fun getUser(userId: String): User = withContext(Dispatchers.IO) {
        try {
            val user = apiService.getUser(userId)
            userDao.insertUser(user)
            user
        } catch (e: Exception) {
            // Fallback to cached data
            userDao.getUser(userId) ?: throw e
        }
    }

    suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
        try {
            val users = apiService.getUsers()
            userDao.insertUsers(users)
            users
        } catch (e: Exception) {
            userDao.getAllUsers()
        }
    }

    suspend fun updateUser(user: User): User = withContext(Dispatchers.IO) {
        val updatedUser = apiService.updateUser(user)
        userDao.updateUser(updatedUser)
        updatedUser
    }

    suspend fun deleteUser(userId: String) = withContext(Dispatchers.IO) {
        apiService.deleteUser(userId)
        userDao.deleteUser(userId)
    }
}

Best Practices

Code Style

// Use meaningful names
class UserManager {                    // Good
    fun getUserById(id: String): User  // Good
    fun isValidEmail(email: String): Boolean // Good
}

class UM {                            // Bad
    fun get(i: String): User          // Bad
    fun check(e: String): Boolean     // Bad
}

// Use type inference when possible
val name = "John"                     // Good
val name: String = "John"            // Unnecessary

// Use data classes for simple data holders
data class User(val id: String, val name: String, val email: String)

// Use sealed classes for restricted hierarchies
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

// Use extension functions for utility methods
fun String.isValidEmail(): Boolean {
    return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}

// Use scope functions appropriately
val user = User("1", "John", "john@example.com").apply {
    // Configure user
}

user?.let { nonNullUser ->
    // Process non-null user
}

Null Safety Best Practices

// Prefer safe calls over explicit null checks
// Good
val length = name?.length

// Less preferred
val length = if (name != null) name.length else null

// Use Elvis operator for default values
val displayName = user.name ?: "Anonymous"

// Use let for null-safe operations
user?.let { nonNullUser ->
    processUser(nonNullUser)
}

// Avoid not-null assertion operator unless absolutely necessary
val definitelyNotNull = value!! // Use sparingly

// Use lateinit for properties that will be initialized later
class MyActivity : Activity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
    }
}

Performance Best Practices

// Use lazy initialization for expensive operations
class DataManager {
    private val expensiveResource by lazy {
        createExpensiveResource()
    }

    private fun createExpensiveResource(): ExpensiveResource {
        // Expensive initialization
        return ExpensiveResource()
    }
}

// Use inline functions for higher-order functions
inline fun <T> measureTime(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    val time = System.currentTimeMillis() - start
    return result to time
}

// Use data classes for better performance with collections
data class Point(val x: Int, val y: Int)

val points = setOf(Point(1, 2), Point(3, 4), Point(1, 2))
// Automatic equals() and hashCode() implementation

// Use appropriate collection types
val uniqueItems = setOf(1, 2, 3)        // For unique items
val orderedItems = listOf(1, 2, 3)      // For ordered items
val keyValuePairs = mapOf("a" to 1)     // For key-value pairs

// Use sequences for large collections with multiple operations
val result = (1..1000000)
    .asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(10)
    .toList()

Coroutines Best Practices

// Use appropriate dispatchers
class UserRepository {
    suspend fun getUser(id: String): User = withContext(Dispatchers.IO) {
        // IO operation
        apiService.getUser(id)
    }

    suspend fun processUserData(data: List<User>): List<ProcessedUser> = 
        withContext(Dispatchers.Default) {
            // CPU-intensive operation
            data.map { processUser(it) }
        }
}

// Handle exceptions properly
suspend fun fetchDataSafely(): Result<Data> {
    return try {
        val data = apiService.fetchData()
        Result.Success(data)
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// Use structured concurrency
class DataLoader {
    suspend fun loadAllData(): CombinedData = coroutineScope {
        val userData = async { loadUserData() }
        val settingsData = async { loadSettingsData() }
        val preferencesData = async { loadPreferencesData() }

        CombinedData(
            users = userData.await(),
            settings = settingsData.await(),
            preferences = preferencesData.await()
        )
    }
}

// Cancel coroutines when no longer needed
class MyViewModel : ViewModel() {
    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.Main + job)

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

Summary

Kotlin is a modern, concise, and safe programming language that offers significant advantages for Android development and beyond. Key features include:

  • Null Safety: Built-in null safety prevents NullPointerException at compile time
  • Conciseness: Reduces boilerplate code significantly compared to Java
  • Interoperability: 100% interoperable with Java, allowing gradual migration
  • Coroutines: Built-in support for asynchronous programming with coroutines
  • Type Inference: Smart type inference reduces explicit type declarations
  • Extension Functions: Add functionality to existing classes without inheritance
  • Data Classes: Automatic generation of equals(), hashCode(), toString(), and copy()
  • Sealed Classes: Restricted class hierarchies for better type safety
  • Smart Casts: Automatic type casting after null checks or type checks
  • Functional Programming: First-class support for functional programming concepts

Kotlin combines object-oriented and functional programming features, making it an excellent choice for modern Android development, server-side development, and multiplatform projects.