Kotlin Advanced Concepts and Practical Patterns

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 context
  • launch: Fire-and-forget coroutine
  • async/await: Concurrent computation with result
  • suspend: Marks functions that can pause
  • withContext: 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...")
    }
}

Tags: kotlin Coroutines Jetpack Compose viewmodel livedata

Posted on Sun, 09 Aug 2026 16:34:08 +0000 by CooKies37