Amol Pawar

Kotlin Generics Made Easy

Kotlin Generics Made Easy: Functions, Properties, and Best Practices

Generics in Kotlin can seem complex at first, but once you understand their power, they make your code more flexible and reusable. In this blog, we’ll break down generics in a simple way, focusing on generic functions and properties and best practices. What Are Generics in Kotlin? Generics allow you to write code that works with...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Non-Null Type Parameters

Kotlin Tip: How to Make Type Parameters Non-Null the Right Way

Kotlin is a powerful language that puts a strong emphasis on null safety. However, when working with generics, it’s easy to accidentally allow nullability, even when you don’t intend to. If you’re wondering how to enforce non-null type parameters properly, this guide will walk you through the right approach. Why Enforcing Non-Null Type Parameters Matters...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Reification

Why Reification Works Only for Inline Functions

Reification is a powerful concept in Kotlin that allows us to retain generic type information at runtime. However, it comes with a significant limitation: it only works for inline functions. But why is that the case? Let’s explore the reasons behind this restriction and understand how reification truly works. Understanding Reification In most JVM-based languages,...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Kotlin Companion Object Explained

Kotlin Companion Object Explained: The Ultimate Guide for Beginners

Kotlin has gained immense popularity for its modern and expressive syntax, making Android development and general programming more efficient. One of its unique features is the companion object, which allows defining static-like members within a class. If you’re a beginner looking to understand Kotlin companion objects, this guide will take you through every essential detail.

What is a Kotlin Companion Object?

In Kotlin, unlike Java, there are no static methods. Instead, Kotlin provides a way to achieve similar functionality using companion objects. A Kotlin Companion Object is an object associated with a class that allows you to access its properties and methods without creating an instance of the class.

Key Characteristics:

  • A companion object is defined inside a class using the companion keyword.
  • It behaves like a singleton, meaning there is only one instance of it per class.
  • You can access its properties and methods using the class name, just like static members in Java.

How to Declare a Companion Object

Declaring a companion object in Kotlin is simple. Use the companion object keyword inside a class.

Kotlin
class MyClass {
    companion object {
        fun sayHello() {
            println("Hello from Companion Object!")
        }
    }
}

fun main() {
    MyClass.sayHello()  // Calling the function without creating an instance
}

Here,

  • The companion object inside MyClass defines a function sayHello().
  • MyClass.sayHello() is called directly, without creating an instance.

This behavior is similar to Java’s static methods but follows Kotlin’s object-oriented approach.

Adding Properties in a Companion Object

You can also define properties inside a companion object.

Kotlin
class Config {
    companion object {
        val APP_NAME = "My Kotlin App"
        fun getAppInfo() = "App Name: $APP_NAME"
    }
}

fun main() {
    println(Config.getAppInfo())  // Output: App Name: My Kotlin App
}

Here,

  • APP_NAME is a constant-like property inside the companion object.
  • The function getAppInfo() returns information about the app.
  • We access both the function and property without creating an instance.

Why Use Companion Objects?

Kotlin Companion Objects provide several benefits:

1. Encapsulation of Utility Functions

  • You can keep utility methods inside a companion object instead of using a separate utility class.

2. Shared State Across Instances

  • A companion object’s properties persist across instances, making them useful for shared constants or configurations.

3. Factory Pattern Implementation

  • Companion objects can be used to create factory methods for object creation.

Using Companion Objects as a Factory

A common use case of companion objects is implementing the Factory Pattern, which allows controlled object creation.

Kotlin
class Car(val model: String, val year: Int) {
    companion object {
        fun createTesla(): Car {
            return Car("Tesla Model S", 2024)
        }
    }
}

fun main() {
    val myCar = Car.createTesla()
    println("Model: ${myCar.model}, Year: ${myCar.year}")
}

Here,

  • Instead of creating a Car object manually, we use Car.createTesla().
  • This ensures consistency when creating pre-defined instances.

Adding Interface Implementation in a Companion Object

A companion object can also implement interfaces, which is useful when you need to enforce a contract.

Kotlin
interface Logger {
    fun log(message: String)
}

class Service {
    companion object : Logger {
        override fun log(message: String) {
            println("Log: $message")
        }
    }
}

fun main() {
    Service.log("Service started")
}
  • The companion object implements the Logger interface.
  • Now, Service.log("Service started") logs a message without an instance.

Conclusion

Kotlin companion objects provide a powerful way to create static-like functionality while keeping an object-oriented structure. They enable defining functions, properties, factory methods, and interface implementations within a class, making code more readable and maintainable.

Now that you have a clear understanding, start using companion objects in your Kotlin projects and take advantage of their benefits..!

Star Projection in Kotlin

Star Projection in Kotlin: Using * Instead of a Type Argument

Generics in Kotlin add flexibility and type safety, but sometimes we don’t need to specify a type. This is where star projection (*) comes in. In this blog, we’ll explore star projection in Kotlin, its use cases, and practical examples to help you understand how and when to use it. Understanding Star Projection in Kotlin In...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Coroutine Builders

Kotlin Coroutine Builders Explained: When to Use launch vs async

Kotlin coroutines are a powerful feature that simplify asynchronous programming. They allow developers to write asynchronous code in a sequential manner, making it easier to read and maintain. At the heart of coroutines are coroutine builders, specifically launch and async. These builders define how coroutines are started and managed.

In this blog, we’ll dive deep into the differences between launch and async, when to use them, and best practices for effective coroutine usage.

Understanding Coroutine Builders

A coroutine builder is a function that creates and starts a coroutine. The two primary coroutine builders in Kotlin are:

  • launch: Used for fire-and-forget operations (does not return a result).
  • async: Used for parallel computations that return a result.

Let’s explore both in detail.

launch – Fire and Forget

The launch builder is used when you don’t need a result. It starts a coroutine that runs independently, meaning it does not return a value. However, it returns a Job object, which can be used to manage its lifecycle (e.g., cancellation or waiting for completion).

Key Points:

  • Does not return a result.
  • Returns a Job that can be used for cancellation or waiting.
  • Runs in the background without blocking the main thread.
Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job =
        launch(Dispatchers.IO) {
            val data = fetchData()
            println("Data: $data")
        }

    job.join() // Ensures the coroutine completes before proceeding
    println("Coroutine completed")
}

suspend fun fetchData(): String {
    delay(1000) // Simulating network call
    return "Fetched Data"
}
  1. The coroutine is launched using launch(Dispatchers.IO) { ... }.
  2. The fetchData() function runs asynchronously without blocking the main thread.
  3. job.join() ensures the coroutine completes before proceeding.

Output:

Kotlin
Data: Fetched Data
Coroutine completed

Note: If job.join() is not called, the program might exit before the coroutine completes, depending on the coroutine scope.

async – Returns a Result

The async builder is used when you need a result. It returns a Deferred<T> object, which represents a future result. To retrieve the result, you must call await().

Key Points:

  • Returns a Deferred<T> object, which must be awaited.
  • Used for parallel computations.
  • Must be called within a coroutine scope.
Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = async(Dispatchers.IO) { fetchData() }
    println("Data: ${result.await()}") // Await to get the result
}

suspend fun fetchData(): String {
    delay(1000) // Simulating network call
    return "Fetched Data"
}
  1. async(Dispatchers.IO) { fetchData() } starts an asynchronous task.
  2. It returns a Deferred<String> object.
  3. Calling .await() retrieves the result.

Output:

Kotlin
Data: Fetched Data

Important: If you do not call await(), the coroutine will run, but the result won’t be retrieved.

async for Parallel Execution

One of the most powerful use cases for async is running multiple tasks in parallel.

Running Two Tasks in Parallel

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()

    val deferred1 = async { fetchData1() }
    val deferred2 = async { fetchData2() }

    val result1 = deferred1.await() // Waits for fetchData1
    val result2 = deferred2.await() // Waits for fetchData2

    val endTime = System.currentTimeMillis()

    println("Results: $result1, $result2")
    println("Total time: ${endTime - startTime} ms")
}

suspend fun fetchData1(): String {
    delay(1000) // Simulating API call
    return "Data 1"
}

suspend fun fetchData2(): String {
    delay(1000) // Simulating API call
    return "Data 2"
}

Here,

  1. async { fetchData1() } and async { fetchData2() } start in parallel.
  2. Each coroutine takes 1 second.
  3. Since they run concurrently, the total execution time is ~1 second instead of 2 seconds (if they run sequentially then 2 seconds).

Best Practices for launch and async

  1. Use launch when you don’t need a result (e.g., updating UI, logging, background tasks).
  2. Use async when you need a result and always call await().
  3. For multiple async tasks, start them first, then call await() to maximize concurrency.
  4. Avoid using async outside of structured concurrency unless you explicitly manage its lifecycle, as it can lead to untracked execution, potential memory leaks, or uncaught exceptions.

Conclusion

Kotlin’s launch and async coroutine builders serve distinct purposes:

  • Use launch when you don’t need a result (fire-and-forget).
  • Use async when you need a result (and always call await()).

By understanding the differences and best practices, you can write efficient, safe, and scalable Kotlin applications using coroutines.

variance

Kotlin Variance Made Simple: In, Out, and Star Projections

Kotlin is a powerful programming language that simplifies development while maintaining strong type safety. One of the essential concepts in Kotlin is variance, which helps us understand how generics and subtyping work. If you’ve ever been confused by out, in, or *, and how generics behave in Kotlin, this guide is for you. Variance: generics...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Inline Functions

Understanding Inline Functions in Kotlin: A Beginner’s Guide

Kotlin is well known for its concise syntax, expressive features, and seamless support for functional programming. One of its powerful yet often misunderstood features is inline functions. If you’ve ever worked with higher-order functions in Kotlin, you’ve probably encountered situations where performance becomes a concern due to lambda expressions. This is where inline functions in...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Advanced Exception Handling in Kotlin Coroutines

Advanced Exception Handling in Kotlin Coroutines: Going Beyond Try-Catch

Kotlin Coroutines simplify asynchronous programming, but handling exceptions effectively is crucial to prevent crashes and unexpected behavior. Many developers rely on try-catch, but coroutines offer more powerful ways to manage exceptions. This post explores advanced techniques for Exception Handling in Kotlin Coroutines, ensuring robust and resilient applications.

Understanding Exception Handling in Kotlin Coroutines

Kotlin Coroutines introduce structured concurrency, which changes how exceptions propagate. Unlike traditional threading models, coroutine exceptions bubble up to their parent by default. However, handling them efficiently requires more than a simple try-catch.

Basic Try-Catch in Coroutines

Before diving into advanced techniques, let’s look at the basic approach:

Kotlin
suspend fun fetchData() {
    try {
        val result = withContext(Dispatchers.IO) { riskyOperation() }
        println("Data: $result")
    } catch (e: Exception) {
        println("Caught exception: ${e.message}")
    }
}

This works but doesn’t leverage coroutine-specific features. Let’s explore better alternatives.

Using CoroutineExceptionHandler

Kotlin provides CoroutineExceptionHandler to catch uncaught exceptions in coroutines. However, it works only for launch, not async.

Kotlin
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    println("Caught exception in handler: ${exception.localizedMessage}")
}

fun main() = runBlocking {
    val scope = CoroutineScope(Job() + Dispatchers.Default + exceptionHandler)
    
    scope.launch {
        throw RuntimeException("Something went wrong")
    }

    delay(100) // Give time for the exception to be handled
}

Why Use CoroutineExceptionHandler?

  • It catches uncaught exceptions from launch coroutines.
  • It prevents app crashes by handling errors at the scope level.
  • Works well with structured concurrency if used at the root scope.

It doesn’t work for async, as deferred results require explicit handling.

Handling Exceptions in async

Unlike launch, async returns a Deferred result, meaning exceptions won’t be thrown until await() is called.

Kotlin
val deferred = async {
    throw RuntimeException("Deferred error")
}

try {
    deferred.await()
} catch (e: Exception) {
    println("Caught exception: ${e.message}")
}

To ensure safety, always wrap await() in a try-catch block or use structured exception handling mechanisms.

SupervisorJob for Independent Child Coroutines

By default, when a child coroutine fails, it cancels the entire parent scope. However, a SupervisorJob allows independent coroutine failures without affecting other coroutines in the same scope.

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(supervisor + Dispatchers.Default) // Ensuring a dispatcher

    val job1 = scope.launch {
        delay(500)
        throw IllegalStateException("Job 1 failed")
    }

    val job2 = scope.launch {
        delay(1000)
        println("Job 2 completed successfully")
    }

    job1.join() // Wait for Job 1 (it will fail)
    job2.join() // Wait for Job 2 (should still succeed)
}

How It Works

  • Without SupervisorJob: If one coroutine fails, the entire scope is canceled, stopping all child coroutines.
  • With SupervisorJob: A coroutine failure does not affect others, allowing independent execution.

Why Use SupervisorJob?

Prevents cascading failures — a single failure doesn’t cancel the whole scope.
Allows independent coroutines — useful when tasks should run separately, even if one fails.

Using supervisorScope for Localized Error Handling

Instead of using SupervisorJob, we can use supervisorScope, which provides similar behavior but at the coroutine scope level rather than the job level:

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {  // Creates a temporary supervisor scope
        launch {
            throw Exception("Failed task") // This coroutine fails
        }
        launch {
            delay(1000)
            println("Other task completed successfully") // This will still execute
        }
    }
}

If one child fails, other children keep running (unlike a regular CoroutineScope). Exceptions are still propagated to the parent scope if unhandled.

When to Use Each?

  • Use SupervisorJob when you need a long-lived CoroutineScope (e.g., ViewModel, Application scope).
  • Use supervisorScope when you need temporary failure isolation inside an existing coroutine.

Best Practices for Exception Handling in Kotlin Coroutines

  1. Use CoroutineExceptionHandler for launch-based coroutines.
  2. Handle exceptions explicitly when using async.
  3. Leverage SupervisorJob to prevent cascading failures.
  4. Wrap critical code inside supervisorScope when needed.
  5. Log errors properly instead of just printing them.
  6. Always clean up resources (e.g., closing network connections) using finally.

Conclusion

Exception Handling in Kotlin Coroutines is more than just try-catch. With CoroutineExceptionHandler, SupervisorJob, and supervisorScope, you can write robust and resilient coroutine-based applications. Implement these best practices to ensure your coroutines handle failures gracefully, keeping your app stable and efficient.

Underscore (_) Operator for Type Arguments

Kotlin’s Underscore (_) Operator for Type Arguments: The Hidden Gem of Generics

Kotlin is packed with features that make it both powerful and expressive. One such hidden gem is the underscore (_) operator for type arguments in Kotlin. While it may not be as widely known as other features, it provides a concise and efficient way to work with generics.

In this post, we’ll explore what this operator does, why it’s useful, and how you can leverage it to simplify your Kotlin code.

What is the Underscore (_) Operator for Type Arguments in Kotlin?

In Kotlin, when working with generics, you often have to specify the exact type argument. However, sometimes you just want Kotlin to infer the type for you without explicitly providing it. The underscore (_) operator allows you to do just that—it acts as a placeholder for type arguments.

The syntax looks like this:

Kotlin
val list: List<_> = listOf(1, 2, 3)

Here, instead of specifying List<Int>, we use _, and Kotlin automatically infers that the list contains integers.

Understanding Underscore ( _ ) Operator for type arguments with Example

The underscore operator _ in Kotlin is a type inference placeholder that allows the Kotlin compiler to automatically infer the type of an argument based on the context and other explicitly specified types.

Kotlin
abstract class SomeClass<T> {
    abstract fun execute() : T
}

class SomeImplementation : SomeClass<String>() {
    override fun execute(): String = "Test"
}

class OtherImplementation : SomeClass<Int>() {
    override fun execute(): Int = 42
}

object Runner {
    inline fun <reified S: SomeClass<T>, T> run() : T {
        return S::class.java.getDeclaredConstructor().newInstance().execute()
    }
}

fun main() {
    // T is inferred as String because SomeImplementation derives from SomeClass<String>
    val s = Runner.run<SomeImplementation, _>()
    assert(s == "Test")
    // T is inferred as Int because OtherImplementation derives from SomeClass<Int>
    val n = Runner.run<OtherImplementation, _>()
    assert(n == 42)
}

Don’t worry! Let’s break down the code step by step:

  1. In this code, we have an abstract class called SomeClass with a generic type T. It declares an abstract function execute() that returns an object of type T.
  2. We have a class called SomeImplementation which extends SomeClass and specifies the generic type as String. It overrides the execute() function and returns the string value "Test".
  3. Similarly, we have another class called OtherImplementation which extends SomeClass and specifies the generic type as Int. It overrides the execute() function and returns the integer value 42.
  4. Below that, we have an object called Runner with a function run(). This function is generic and has two type parameters S and T. It uses the reified keyword to access the type information at runtime. Inside the function, it creates an instance of the specified class S using reflection (getDeclaredConstructor().newInstance()) and calls the execute() function on it, returning the result of type T.

In the above code, the underscore operator is used in the main() function when calling the Runner.run() function. Let’s take a closer look:

Kotlin
val s = Runner.run<SomeImplementation, _>()

In this line, the type parameter T is explicitly specified as _ for the Runner.run() function. Here, _ acts as a placeholder for the type to be inferred by the compiler. Since SomeImplementation derives from SomeClass<String>, the compiler infers T as String for this invocation. Therefore, the variable s is inferred to be of type String, and the Runner.run() function returns the result of executing SomeImplementation, which is the string "Test".

Kotlin
val n = Runner.run<OtherImplementation, _>()

Similarly, in this line, the type parameter T is specified as _ for the Runner.run() function. Since OtherImplementation derives from SomeClass<Int>, the compiler infers T as Int for this invocation. Consequently, the variable n is inferred to be of type Int, and the Runner.run() function returns the result of executing OtherImplementation, which is the integer 42.

By using the underscore operator _ as a type argument, the compiler can automatically infer the appropriate type based on the context and the explicitly specified types.

Why Use the Underscore (_) Operator for Type Arguments in Kotlin?

Using the underscore operator has several benefits:

  1. Simplifies Type Declarations — You don’t have to explicitly specify type arguments when they can be inferred.
  2. Improves Code Readability — It makes code cleaner and easier to read, especially when dealing with complex generics.
  3. Reduces Boilerplate Code — Less repetitive type annotations mean more concise code.
  4. Works with Generic Functions — It allows you to call generic functions without explicitly passing type arguments.

How to Use the Underscore (_) Operator in Kotlin

1. Using _ with Generic Functions

Kotlin lets you use _ when calling generic functions. Suppose you have a function that takes a generic type parameter:

Kotlin
fun <T> printType(value: T) {
    println("Type: ${value::class.simpleName}")
}

You can call this function without explicitly specifying the type argument:

Kotlin
printType<_>(42) // Kotlin infers the type as Int

2. Using _ with Collections

The underscore operator works well with collections, making them more flexible when type inference is possible.

Kotlin
val numbers: List<_> = listOf(10, 20, 30)
println(numbers) // Output: [10, 20, 30]

Here, Kotlin automatically infers that numbers is a List<Int>.

3. _ in Function Returns

If a function returns a generic type, _ allows Kotlin to infer it without explicit declaration.

Kotlin
fun getList(): List<_> = listOf("Kotlin", "Java", "Swift")

Kotlin infers that getList() returns a List<String> without us specifying it explicitly.

When to Avoid Using the Underscore (_) Operator in Kotlin

While the underscore operator is useful, there are situations where avoiding it can improve code clarity and maintainability:

  • When Type Inference Fails — If Kotlin cannot determine the type of a variable or lambda parameter, using _ will result in a compilation error.
  • In Public APIs — Overusing _ in public functions can make the API less clear, potentially confusing users who rely on explicit parameter names for readability.
  • When Explicit Types Improve Readability — In complex expressions or function signatures, explicitly defining types can enhance code comprehension and maintainability.

Conclusion

The underscore (_) operator for type arguments in Kotlin is a simple yet powerful tool that helps reduce boilerplate code and improve readability. Whether you’re working with collections, or generic functions, it can make your code cleaner and more concise.

Next time you’re dealing with generics, give _ a try and let Kotlin do the heavy lifting for you..!

error: Content is protected !!