Kotlin prvoides multiple ways to declare and manage properties with different initialization behaviors.
// Late-initialized property: must be assigned before use, can be reassigned
lateinit var userData: String
// Delegated property initialized lazily on first access
val userName: String by lazy { "sherlbon" }
// Nullable property with explicit null initialization
var optionalValue: Int? = null
// Custom getter and setter
var computedValue: Int
get() = 42
set(value) {
field = if (value > 0) value else 0
}
// Lazy computation with multiple steps
val sumResult: Int by lazy {
val x = 15
val y = 25
x + y
}
True Constants in Kotlin
Using val does not guarantee compile-time constants. The following is not a constant:
// ❌ Not a constant — evaluated at runtime each time
val currentTime: Long
get() = System.currentTimeMillis()
True compile-time constants require either const or @JvmField:
// ✅ Compile-time constant (only for primitive and String types)
const val MAX_RETRY_COUNT = 3
private const val API_ENDPOINT = "https://api.example.com"
// ✅ JVM static field — visible as a static field in Java
@JvmField val DEFAULT_TIMEOUT = 5000L
Loop Constructs
// Range iteration
for (i in 1..10) println(i) // 1 to 10 inclusive
for (i in 1 until 10) println(i) // 1 to 9
for (i in 10 downTo 1) println(i) // 10 down to 1
for (i in 1..10 step 2) println(i) // 1, 3, 5, 7, 9
// Collection iteration
val items = listOf("a", "b", "c", "d")
for (item in items) println(item)
// Indexed iteration
for ((index, item) in items.withIndex()) {
println("[$index]: $item")
}
// Repeat N times
repeat(5) { println("Iteration $it") }
Constructors in Custom Views
Kotlin supports concise constructor delegation and default parameters:
open class CustomView(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val rootLayout: RelativeLayout by lazy {
RelativeLayout(context).apply {
id = View.generateViewId()
layoutParams = ViewGroup.LayoutParams(
MATCH_PARENT,
MATCH_PARENT
)
}
}
init {
setupLayout()
}
private fun setupLayout() {
addView(rootLayout)
}
}
Static Methods and Companion Objects
Kotlin does not have static methods, but provides alternatives:
// Option 1: Object declaration
object NetworkUtils {
@JvmStatic
fun fetch(url: String): String {
return "Response from $url"
}
}
// Option 2: Companion object
class DataManager {
companion object {
@JvmStatic
fun save(data: String) {
// implementation
}
}
}
// Option 3: Top-level function (package-scoped)
fun clearCache() {
// accessible directly from Java as UtilsKt.clearCache()
}
Extension Functions
Extend existing types without inheritance:
// Extend View with a utility function
internal fun View.showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
// Generic extension with lambda
inline fun <T, R> T.letIfNonNull(block: (T) -> R): R? {
return if (this != null) block(this) else null
}
// Usage
val name: String? = "Alice"
name.letIfNonNull { it.uppercase() }?.let { println(it) }
// Receiver-based lambda: buildString example
fun buildText(block: StringBuilder.() -> Unit): String {
return StringBuilder().apply(block).toString()
}
val result = buildText {
append("Hello")
append(" ")
append("World")
}
Scope Functions: let, run, apply, also, with
| Function | Receiver | Return Value | Reference to Receiver |
|---|---|---|---|
let |
it | lambda result | Yes |
run |
this | lambda result | No |
apply |
this | receiver object | No |
also |
it | receiver object | Yes |
with |
this | lambda result | No |
data class Person(var name: String, var age: Int)
val person = Person("Alice", 25)
// let: transform and return result
val nameLength = person.let { it.name.length }
// apply: configure object and return it
person.apply {
age = 26
name = "Alice Smith"
}
// also: perform side effect and return object
person.also { println("Updated: ${it.name}") }
// with: execute block in context of object
with(person) {
println("$name is $age years old")
}
Varargs and Default Parameters
// Accept variable number of arguments
fun logMessages(vararg messages: String) {
messages.forEach { println(it) }
}
logMessages("Error", "Warning", "Info")
// Default parameters reduce constructor overloads
data class Config(
val host: String = "localhost",
val port: Int = 8080,
val timeout: Long = 5000
)
val config = Config(port = 9090) // Uses defaults for host and timeout
Singleton Patterns
// Thread-safe singleton with lazy initialization
class DatabaseManager private constructor() {
companion object {
val INSTANCE: DatabaseManager by lazy { DatabaseManager() }
}
}
// Parameterized singleton using holder pattern
class NetworkClient private constructor(val baseUrl: String) {
companion object : SingletonHolder<NetworkClient, String>(::NetworkClient)
}
// Usage
val client = NetworkClient.getInstance("https://api.example.com")
Generics: In, Out, and Reified
Use variance annotations to control type safety:
// Out: covariant — only produces T (return type)
interface Producer<out T> {
fun produce(): T
}
// In: contravariant — only consumes T (parameter)
interface Consumer<in T> {
fun consume(item: T)
}
// Invariant: both produces and consumes
interface Processor<T> {
fun produce(): T
fun consume(item: T)
}
// Reified type parameter for runtime type access
inline fun <reified T> Gson.fromJson(json: String): T {
return fromJson(json, T::class.java)
}
val user: User = gson.fromJson(jsonString)
Higher-Order Functions and Inline Lambdas
Functions accepting or returning other functions:
// Function taking lambda as parameter
inline fun executeWithLogging(operation: () -> Unit) {
println("Starting operation...")
operation()
println("Operation complete.")
}
// Inline function with lambda
inline fun <T> T.runIfNotNull(block: (T) -> Unit) {
if (this != null) block(this)
}
val data: String? = "Hello"
data.runIfNotNull { println(it.length) }
// Returning a function
fun createMultiplier(factor: Int): (Int) -> Int {
return { it * factor }
}
val double = createMultiplier(2)
println(double(5)) // 10
Anonymous Functions vs Lambdas
// Anonymous function (explicit return type)
val add = fun(a: Int, b: Int): Int {
return a + b
}
// Lambda expression (implicit)
val subtract = { a: Int, b: Int -> a - b }
// Single-expression lambda
val multiply = { a: Int, b: Int -> a * b }
// Single-parameter lambda with implicit 'it'
val greet = { "Hello, $it!" }
println(greet("World"))
// Function reference
fun sum(a: Int, b: Int) = a + b
val operation: (Int, Int) -> Int = ::sum
println(operation(3, 4)) // 7
Coroutines and Concurrency
Key coroutine primitives:
runBlocking: Entry point to launch coroutine contextlaunch: Fire-and-forget coroutineasync/await: Concurrent computation with resultsuspend: Marks functions that can pausewithContext: Switch dispatcher (IO, Default, Main)coroutineScope: Structured concurrency scope
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(1000)
println("Done after 1s")
}
val deferred = async {
delay(500)
"Result from async"
}
println(deferred.await())
job.join()
}
Jetpack and Modern Android Architecture
ViewModel + LiveData
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
fun loadUser(id: String) {
viewModelScope.launch {
_user.value = repository.getUser(id)
}
}
}
Room Database with LiveData
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE id = :id")
fun findById(id: Int): LiveData<User>
}
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Paging 3 with Flow
class UserDataSource(
private val api: ApiService
) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
return try {
val response = api.getUsers(
page = params.key ?: 1,
size = params.loadSize
)
LoadResult.Page(
data = response.users,
prevKey = if (params.key == 1) null else params.key - 1,
nextKey = response.nextPage
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
Jetpack Compose UI
@Composable
fun Greeting(name: String) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = "Hello, $name!", style = MaterialTheme.typography.h6)
Text(text = "Welcome to Compose!", style = MaterialTheme.typography.body1)
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
Greeting("Android")
}
Essential Libraries
- Coil: Modern image loading with Coroutines
- OkHttp + Retrofit: HTTP client with Kotlin-first design
- Koin: Lightweight DI using DSL and reified types
- FlowBinding: Reactive streams using Kotlin Flow instead of RxJava
- LeakCanary: Memory leak detection
- Android KTX: Official Kotlin extensions for Android APIs
- Arrow: Functional programming utilitiees
- MVI frameworks (Orbit-MVI, ReKotlin): Unidirectional data flow
Design Patterns in Kotlin
Builder Pattern (Simplified)
data class User(
val name: String = "Guest",
val email: String = "",
val age: Int = 0,
val isActive: Boolean = true
)
// No need for builder class — use default parameters
val user = User(name = "John", age = 30)
Decorator via Extension
fun TextView.setBold() {
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
}
// Usage
textView.setBold()
Strategy Pattern with Function Types
class PaymentProcessor(val strategy: () -> Unit) {
fun process() = strategy()
}
val creditStrategy = { println("Processing credit card...") }
val paypalStrategy = { println("Processing PayPal...") }
val processor = PaymentProcessor(creditStrategy)
processor.process()
Delegation with by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println("[LOG] $message")
}
class AppService(private val logger: Logger) : Logger by logger {
fun doWork() {
log("Working...")
}
}