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
- Basic Syntax
- Variables and Constants
- Data Types
- Control Flow
- Functions
- Classes and Objects
- Inheritance
- Interfaces
- Data Classes
- Sealed Classes
- Generics
- Collections
- Null Safety
- Extension Functions
- Higher-Order Functions
- Coroutines
- Android Development
- Best Practices
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.