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...
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...
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,...
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
classMyClass {companionobject {funsayHello() {println("Hello from Companion Object!") } }}funmain() { 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.
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..!
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...
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.
The coroutine is launched using launch(Dispatchers.IO) { ... }.
The fetchData() function runs asynchronously without blocking the main thread.
job.join() ensures the coroutine completes before proceeding.
Output:
Kotlin
Data: FetchedDataCoroutine 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.*funmain() = runBlocking {val result = async(Dispatchers.IO) { fetchData() }println("Data: ${result.await()}") // Await to get the result}suspendfunfetchData(): String {delay(1000) // Simulating network callreturn"Fetched Data"}
async(Dispatchers.IO) { fetchData() } starts an asynchronous task.
It returns a Deferred<String> object.
Calling .await() retrieves the result.
Output:
Kotlin
Data: FetchedData
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.
async { fetchData1() } and async { fetchData2() } start in parallel.
Each coroutine takes 1 second.
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
Use launch when you don’t need a result (e.g., updating UI, logging, background tasks).
Use async when you need a result and always call await().
For multiple async tasks, start them first, then call await() to maximize concurrency.
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.
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...
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...
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:
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}")}funmain() = runBlocking {val scope = CoroutineScope(Job() + Dispatchers.Default + exceptionHandler) scope.launch {throwRuntimeException("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.
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.*funmain() = runBlocking {val supervisor = SupervisorJob()val scope = CoroutineScope(supervisor + Dispatchers.Default) // Ensuring a dispatcherval job1 = scope.launch {delay(500)throwIllegalStateException("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.*funmain() = runBlocking {supervisorScope { // Creates a temporary supervisor scopelaunch {throwException("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
Use CoroutineExceptionHandler for launch-based coroutines.
Handle exceptions explicitly when using async.
Leverage SupervisorJob to prevent cascading failures.
Wrap critical code inside supervisorScope when needed.
Log errors properly instead of just printing them.
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.
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
abstractclassSomeClass<T> {abstractfunexecute() : T}classSomeImplementation : SomeClass<String>() {overridefunexecute(): String = "Test"}classOtherImplementation : SomeClass<Int>() {overridefunexecute(): Int = 42}objectRunner {inlinefun <reifiedS: SomeClass<T>, T> run() : T {return S::class.java.getDeclaredConstructor().newInstance().execute() }}funmain() {// 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:
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.
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".
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.
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:
Simplifies Type Declarations — You don’t have to explicitly specify type arguments when they can be inferred.
Improves Code Readability — It makes code cleaner and easier to read, especially when dealing with complex generics.
Reduces Boilerplate Code — Less repetitive type annotations mean more concise code.
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 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..!