Kotlin

Kotlin StateFlow and SharedFlow

Kotlin StateFlow and SharedFlow Explained: A Beginner-Friendly Guide

Kotlin’s Flow API has transformed the way we think about asynchronous data streams. But when your app needs to hold state or broadcast events, two specialized types of Flows shine the brightest: StateFlow and SharedFlow.

In this guide, we’ll break down StateFlow and SharedFlow in a simple way. Whether you’re working with Jetpack Compose, MVVM, or traditional Android UI, you’ll understand when and how to use each one — with examples.

StateFlow — Holds a State

StateFlow is similar to LiveData and is used to store a single state that updates over time.

Kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val stateFlow = MutableStateFlow(0)

    val job = launch {
        stateFlow.collect { value ->
            println("Collector received: $value")
        }
    }

    delay(500)
    stateFlow.value = 1
    delay(500)
    stateFlow.value = 2
    delay(500)
    job.cancel()
}


//Output

Collector received: 0
Collector received: 1
Collector received: 2

StateFlow always emits the latest value to collectors. It behaves like LiveData, and collectors receive the current state immediately upon subscription.

SharedFlow — Broadcasts Data to Multiple Collectors

Kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    // Create a MutableSharedFlow with a buffer capacity of 2
    val sharedFlow = MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 2
    )

    val job1 = launch {
        sharedFlow.collect { value ->
            println("Collector 1 received: $value")
        }
    }

    val job2 = launch {
        sharedFlow.collect { value ->
            println("Collector 2 received: $value")
        }
    }

    // Delay to ensure collectors are active before emitting
    delay(100)

    sharedFlow.emit(10)
    sharedFlow.emit(20)

    delay(500)

    job1.cancel()
    job2.cancel()
}

// Output

Collector 1 received: 10
Collector 2 received: 10
Collector 1 received: 20
Collector 2 received: 20

By default, MutableSharedFlow has no buffer and replay = 0, meaning it won’t emit any value unless a collector is ready at the moment of emission.

  • We set extraBufferCapacity = 2, allowing SharedFlow to buffer a couple of values while the collectors start.
  • We add delay(100) before emitting, ensuring both collectors are already collecting.

This way, both collectors reliably receive all values.

SharedFlow broadcasts emitted values to all active collectors. It’s great for one-time events (like navigation, snackbars, etc.), and multiple collectors will receive the same emissions.

Conclusion

Kotlin’s StateFlow and SharedFlow are powerful tools to build reactive UIs that are predictable, scalable, and cleanly architected. Understanding the difference between state and event helps you choose the right flow for the right situation.

How to Build a Weather App in Kotlin

How to Build a Weather App in Kotlin Using MVVM, Retrofit, and Room

Building an Android app that fetches live weather data is a great way to get hands-on with networking, local storage, and clean architecture principles. In this guide, we’ll build a Weather App in Kotlin using MVVM, Retrofit for API calls, and Room for local caching. It’s a solid project for interviews or real-world use — especially if you’re still working with older setups and haven’t yet moved to Clean + MVVM Architecture, and Jetpack Compose.

Technical Disclaimer:
The code snippets shared here are from an older codebase, so things like the API or the implementation might be different now. If you’re working with the latest tech stack, you’ll probably need to make a few tweaks to the code at different levels.

Weather App Project Overview

Our Weather App will:

  • Fetch weather data from OpenWeatherMap API.
  • Use MVVM (Model-View-ViewModel) architecture for clean code separation.
  • Implement Retrofit for API communication.
  • Store data locally using Room Database for offline access.
  • Display a list of weather forecasts with RecyclerView.
  • Ensure smooth UI updates using LiveData.

Project Structure

Kotlin
WeatherApp/
│── api/
│   ├── WeatherService.kt
│   ├── ApiClient.kt
│── model/
│   ├── WeatherResponse.kt
│── repository/
│   ├── WeatherRepository.kt
│── viewmodel/
│   ├── WeatherViewModel.kt
│── view/
│   ├── MainActivity.kt
│   ├── WeatherAdapter.kt
│── database/
│   ├── WeatherDao.kt
│   ├── WeatherDatabase.kt
│── utils/
│   ├── Constants.kt
│── res/
│   ├── layout/
│   │   ├── activity_main.xml
│   │   ├── item_weather.xml

Setting Up Retrofit for API Calls

First, we need to integrate Retrofit to fetch weather data from OpenWeatherMap.

Add Dependencies

Add these dependencies to your build.gradle (Module: app) file:

Kotlin
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
}

Create API Interface

Kotlin
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

interface WeatherService {
    @GET("weather")
    suspend fun getWeather(
        @Query("q") city: String,
        @Query("appid") apiKey: String,
        @Query("units") units: String = "metric"
    ): WeatherResponse
}

Create Retrofit Client

Kotlin
object ApiClient {
    private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"

    val instance: WeatherService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(WeatherService::class.java)
    }
}

This setup allows us to fetch weather data efficiently.

Note:- An API key is mandatory. Make sure to create a valid API key — if you use an invalid one or skip it entirely, the API will throw an error response like this:

JSON
{
  "cod": 401,
  "message": "Invalid API key. Please see https://openweathermap.org/faq#error401 for more info."
}

Creating Data Models

The API response will be mapped to data classes.

Kotlin
data class WeatherResponse(
    val name: String,
    val main: Main,
    val weather: List<Weather>
)

data class Main(
    val temp: Double
)

data class Weather(
    val description: String
)

Implementing Room Database

To cache weather data, let’s create a Room Database.

Add Dependencies

Kotlin
dependencies {
    implementation 'androidx.room:room-runtime:2.4.2'
    kapt 'androidx.room:room-compiler:2.4.2'
}

Create Entity & DAO

Kotlin
import androidx.room.*

@Entity
data class WeatherEntity(
    @PrimaryKey val city: String,
    val temperature: Double,
    val description: String
)

@Dao
interface WeatherDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertWeather(weather: WeatherEntity)

    @Query("SELECT * FROM WeatherEntity WHERE city = :city")
    suspend fun getWeather(city: String): WeatherEntity?
}

Create Database

Kotlin
@Database(entities = [WeatherEntity::class], version = 1)
abstract class WeatherDatabase : RoomDatabase() {
    abstract fun weatherDao(): WeatherDao
}

Creating Repository

The repository will handle data fetching.

Kotlin
class WeatherRepository(private val api: WeatherService, private val dao: WeatherDao) {

    suspend fun getWeather(city: String, apiKey: String): WeatherEntity {
        val response = api.getWeather(city, apiKey)
        val weatherEntity = WeatherEntity(
            city = response.name,
            temperature = response.main.temp,
            description = response.weather.first().description
        )
        dao.insertWeather(weatherEntity)
        return weatherEntity
    }
}

Implementing ViewModel

Kotlin
class WeatherViewModel(private val repository: WeatherRepository) : ViewModel() {
    private val _weather = MutableLiveData<WeatherEntity>()
    val weather: LiveData<WeatherEntity> get() = _weather
    
    fun fetchWeather(city: String, apiKey: String) {
        viewModelScope.launch {
            val data = repository.getWeather(city, apiKey)
            _weather.postValue(data)
        }
    }
}

Creating UI with RecyclerView

Adapter Class

Kotlin
class WeatherAdapter(private val weatherList: List<WeatherEntity>) : RecyclerView.Adapter<WeatherAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_weather, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val weather = weatherList[position]
        holder.city.text = weather.city
        holder.temp.text = "${weather.temperature}°C"
        holder.desc.text = weather.description
    }
}

Activity Layout

XML
<RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Conclusion

This tutorial covers API integration, MVVM architecture, and local caching. By following these steps, you can build a solid Android app that fetches and stores weather data efficiently.

If you want to take it further, you can experiment with adding new features such as real-time updates, notifications, or even integrating Jetpack Compose for a more modern UI approach. The foundation is now set for you to keep exploring and improving your Android development skills..!

Declaration-Site Variance

Understanding Kotlin Declaration-Site Variance: Kotlin’s In & Out vs. Java Wildcards

Kotlin provides a powerful type system that includes declaration-site variance, making generics more flexible and safe. If you’ve ever worked with generics in Java, you might recall the ? extends and ? super keywords. Kotlin simplifies this with in and out modifiers.

In this blog, we’ll break down declaration-site variance in Kotlin, explore how in and out work, and compare them to Java wildcards to clarify these concepts.

What is Declaration-Site Variance in Kotlin?

In Kotlin, the ability to specify variance modifiers on class declarations provides convenience and consistency because these modifiers apply to all places where the class is used. This concept is known as a declaration-site variance.

Declaration-site variance in Kotlin is achieved by using variance modifiers on type parameters when defining a class. As you already knows there are two main variance modifiers:

  1. out (covariant): Denoted by the out keyword, it allows the type parameter to be used as a return type or read-only property. It specifies that the type parameter can only occur in the “out” position, meaning it can only be returned from functions or accessed in a read-only manner.
  2. in (contravariant): Denoted by the in keyword, it allows the type parameter to be used as a parameter type. It specifies that the type parameter can only occur in the “in” position, meaning it can only be passed as a parameter to functions.

By specifying these variance modifiers on type parameters, you define the variance behavior of the class, and it remains consistent across all usages of the class.

On the other hand, Java handles variance differently through use-site variance. In Java, each usage of a type with a type parameter can specify whether the type parameter can be replaced with its subtypes or supertypes using wildcard types (? extends and ? super). This means that at each usage point of the type, you can decide the variance behavior.

It’s important to note that while Kotlin supports declaration-site variance with the out and in modifiers, it also provides a certain level of use-site variance through the out and in projection syntax (out T and in T). These projections allow you to control the variance behavior in specific usage points within the code.

Declaration-site variance in Kotlin Vs. Java wildcards

In Kotlin, declaration-site variance allows for more concise code because variance modifiers are specified once on the declaration of a class or interface. This means that clients of the class or interface don’t have to think about the variance modifiers. The convenience of declaration-site variance is that the variance behavior is determined at the point of declaration and remains consistent throughout the codebase.

On the other hand, in Java, wildcards are used to handle variance at the use site. To create APIs that behave according to users’ expectations, the library writer has to use wildcards extensively. For example, in the Java 8 standard library, wildcards are used on every use of the Function interface. This can lead to code like Function<? super T, ? extends R> in method signatures.

To illustrate the declaration of the map method in the Stream interface in Java :

Kotlin
/* Java */
public interface Stream<T> {
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
}

In the Java code, wildcards are used in the declaration of the map method to handle the variance of the function argument. This can make the code less readable and more cumbersome, especially when dealing with complex type hierarchies.

In contrast, the Kotlin code uses declaration-site variance, specifying the variance once on the declaration makes the code much more concise and elegant.

Benefits of Declaration-Site Variance in Kotlin

  1. Stronger Type Safety — Prevents incorrect type assignments.
  2. More Readable Code — Clearly defines how a generic class is used.
  3. No Wildcards Needed — Unlike Java’s ? extends and ? super, Kotlin’s variance is built-in.

Conclusion

Understanding declaration-site variance in Kotlin with in and out is crucial for writing type-safe and maintainable generic classes. The out modifier allows a class to only produce values, making it covariant, while in allows a class to only consume values, making it contravariant.

By leveraging these concepts, you can write more flexible, safe, and concise Kotlin code without the complexity of Java’s wildcards.

Restrictions of Reified Type Parameters

Top 4 Restrictions of Reified Type Parameters in Kotlin and How to Work Around Them

Kotlin is a powerful language that brings many improvements over Java, especially when it comes to generics. One of its unique features is reified type parameters, which solve some of the limitations of Java’s generics. However, there are certain restrictions on reified type parameters that developers should be aware of. In this post, we’ll dive deep into these restrictions, understand why they exist, and explore how to work around them.

What Are Reified Type Parameters?

In Kotlin, generic type parameters are usually erased at runtime due to type erasure, just like in Java. However, by using the inline keyword along with the reified modifier, you can retain type information at runtime.

Without Reified Type Parameters

Kotlin
fun <T> getClassType(): Class<T> {
    return T::class.java // Error: Cannot use 'T' as a reified type parameter
}

The above code will not work because T is erased at runtime, making it impossible to retrieve its class type.

With Reified Type Parameters

Kotlin
inline fun <reified T> getClassType(): Class<T> {
    return T::class.java
}

fun main() {
    println(getClassType<String>()) // Output: class java.lang.String
}

By marking T as reified, we can access its type at runtime, allowing operations like T::class.java without issues.

Key Restrictions on Reified Type Parameters

While reified type parameters are incredibly useful, they come with some limitations. Let’s explore these restrictions in detail.

1. Reified Type Parameters Can Only Be Used in Inline Functions

One of the biggest restrictions on reified type parameters is that they are only allowed inside inline functions. If you try to use them in regular functions, you’ll get a compilation error.

Kotlin
fun <reified T> someFunction() { // Error: Type parameter T cannot be reified
    println(T::class.java)
}

Why Does This Restriction Exist?

Kotlin’s reified type parameters work by inlining the function, meaning the actual type is substituted at the call site. Without inlining, the type information would still be erased, making it impossible to retain the generic type at runtime.

Workaround: To use reified types, always mark your function as inline:

Kotlin
inline fun <reified T> someFunction() {
    println(T::class.java)
}

2. Reified Types Cannot Be Used in Class-Level Generics

Reified type parameters are tied to functions, not classes. If you try to use a reified type in a class definition, Kotlin will complain:

Kotlin
class MyClass<reified T> {  // This is not allowed!
    fun printType() {
        println("The type is: ${T::class.simpleName}")
    }
}

Error:

Kotlin
Reified type parameters are only allowed for inline functions.

Workaround: Instead of using a class-level generic with reified, define an inline function inside the class:

Kotlin
class MyClass<T> {
    inline fun <reified T> printType() {
        println("The type is: ${T::class.simpleName}")
    }
}

3. Reified Type Parameters Cannot Be Used in Class-Level Properties

Another important restriction on reified type parameters is that they cannot be used as class properties.

Kotlin
class Example<T> {
    val type = T::class // Error: Cannot use 'T' as a reified type parameter
}

Since reified type parameter require inline functions to retain type information, they cannot be stored in class properties. The class itself does not have access to the type at runtime due to type erasure.

Workaround: If you need to store a type reference, you can pass the KClass instance explicitly:

Kotlin
class Example<T>(private val clazz: Class<T>) {
    fun getType(): Class<T> = clazz
}

fun main() {
    val example = Example(String::class.java)
    println(example.getType()) // Output: class java.lang.String
}

4. Cannot Create New Instances of Reified Types

Unlike Java’s reflection-based approach (T::class.java.newInstance()), Kotlin prevents the direct instantiation of reified types:

Kotlin
inline fun <reified T> createInstance(): T {
    return T()  // Compilation error!
}

Error:

Kotlin
Cannot create an instance of T because it has no zero-argument constructor.

Workaround: Pass a factory function or a class reference:

Kotlin
inline fun <reified T> createInstance(factory: () -> T): T {
    return factory()
}

Conclusion

Reified type parameters are a powerful feature in Kotlin, allowing us to retain type information at runtime. However, there are several restrictions on reified type parameters to be mindful of:

  • Reified types only work in inline functions.
  • They cannot be used in class-level generics or properties.
  • You cannot instantiate a reified type directly.

By keeping these points in mind, you can write more efficient and type-safe Kotlin code while leveraging the full power of reified type parameters.

Object in Kotlin

Understanding Object in Kotlin: A Deep Dive

Kotlin is a powerful and expressive programming language that brings many modern features to Android and backend development. One of its standout features (object in kotlin) is the object keyword, which provides a clean and concise way to define singleton objects, companion objects, and anonymous objects. If you’re coming from Java, understanding how Kotlin handles objects can significantly improve your code’s readability and efficiency.

In this guide, we’ll explore Kotlin’s object keyword in depth, covering its different use cases with examples.

What is an Object in Kotlin?

In Kotlin, an object is an instance of a class that is created automatically and can be referenced directly. Unlike traditional class instantiation, where you need to explicitly create an instance using new, Kotlin’s object keyword allows you to create singletons and anonymous objects efficiently.

Objects in Kotlin can be categorized into:

  • Singleton Objects
  • Companion Objects
  • Anonymous Objects

Each of these serves a different purpose. Let’s break them down one by one.

Singleton Object in Kotlin

A singleton is a class that has only one instance throughout the program. Instead of creating multiple instances, you define a singleton using object.

Kotlin
object Database {
    val name = "MyDatabase"
    
    fun connect() {
        println("Connected to $name")
    }
}

fun main() {
    Database.connect() // Output: Connected to MyDatabase
}
  • No need for manual instantiation — The Database object is automatically created.
  • Thread-safe — Since there is only one instance, it avoids issues with multiple instances.
  • Useful for managing shared resources, such as database connections or network clients.

Companion Objects: Static-like Behavior in Kotlin

Unlike Java, Kotlin does not have static methods. Instead, you can use companion objects inside classes to define functions and properties that belong to the class itself rather than its instances.

Kotlin
class User(val name: String) {
    companion object {
        fun createGuest() = User("Guest")
    }
}

fun main() {
    val guest = User.createGuest()
    println(guest.name) // Output: Guest
}

Why Use Companion Objects?

  • Acts like a static method in Java — You don’t need to create an instance of User to call createGuest().
  • Encapsulates related functionality — Keeps utility functions inside the class instead of defining them separately.
  • Can implement interfaces — Unlike static methods in Java, a companion object can extend interfaces.

Anonymous Objects: One-Time Use Classes 

Sometimes, you need an object only once, such as for event listeners or implementing an interface on the fly. Kotlin provides anonymous objects (Object Expression) for this purpose.

Kotlin
interface ClickListener {
    fun onClick()
}

fun main() {
    val buttonClickListener = object : ClickListener {
        override fun onClick() {
            println("Button clicked!")
        }
    }
    buttonClickListener.onClick() // Output: Button clicked!
}

Key Benefits:

  • No need to create a separate class — Saves boilerplate code.
  • Good for event listeners and callbacks — Common in Android development.
  • Short-lived instances — Automatically garbage collected when no longer needed.

Object Expression vs. Object Declaration

There are two primary ways to use objects in Kotlin: Object Expressions and Object Declarations. Let’s compare them:

FeatureObject ExpressionObject Declaration
InstantiationEvery time usedOnly once (Singleton)
Named?NoYes
Use caseOne-time useGlobal state, shared instance

When to Use Objects in Kotlin?

  • Use a singleton object when you need a single, shared instance (e.g., logging, database connections).
  • Use a companion object when you need static-like methods and properties within a class.
  • Use an anonymous object when you need a temporary implementation of an interface or class (e.g., callbacks, event listeners).

Conclusion

Kotlin’s object keyword is a powerful tool that simplifies singleton patterns, enables static-like functionality, and allows for concise anonymous implementations. By understanding singleton objects, companion objects, and anonymous objects, you can write cleaner, more efficient, and idiomatic Kotlin code.

Kotlin Variance

Kotlin Variance Demystified: Understanding Generics & Subtyping

Kotlin is a powerful, modern programming language with robust support for generics. But when dealing with generics, you often run into the concept of variance, which can be tricky to grasp at first. In this post, we’ll break down Kotlin variance in a simple and engaging way, ensuring you fully understand generics and subtyping.

What is Kotlin Variance?

Variance defines how generic types relate to each other in the context of subtyping. Consider this:

Kotlin
open class Animal
class Dog : Animal()

In normal inheritance, Dog is a subtype of Animal. But does List<Dog> automatically become a subtype of List<Animal>? The answer is no, unless we explicitly declare variance.

Kotlin provides three ways to handle variance:

  • Covariance (out)
  • Contravariance (in)
  • Invariance (default behavior)

Let’s explore each of these concepts with examples.

Covariance (out) – Producer

Covariant types allow a generic type to be a subtype of another generic type when its type parameter is only used as an output (producer). It is declared using the out keyword.

Kotlin
interface Producer<out T> {
    fun produce(): T
}

This means that Producer<Dog> can be used where Producer<Animal> is expected:

Kotlin
class DogProducer : Producer<Dog> {
    override fun produce(): Dog = Dog()
}

val producer: Producer<Animal> = DogProducer() // Works fine

Why Use out?

  • It ensures type safety.
  • Used when a class only returns values of type T.
  • Common in collections like List<T>, which can only produce items, not modify them.

Example:

Kotlin
fun feedAnimals(producer: Producer<Animal>) {
    val animal: Animal = producer.produce()
    println("Feeding ${animal::class.simpleName}") //O/P - Feeding Dog
}

Since Producer<Dog> is a subtype of Producer<Animal>, this works without any issues.

Contravariance (in) – Consumer

Contravariant types allow a generic type to be a supertype of another generic type when its type parameter is only used as an input (consumer). It is declared using the in keyword.

Kotlin
interface Consumer<in T> {
    fun consume(item: T)
}

This means that Consumer<Animal> can be used where Consumer<Dog> is expected:

Kotlin
class AnimalConsumer : Consumer<Animal> {
    override fun consume(item: Animal) {
        println("Consuming ${item::class.simpleName}")
    }
}

val consumer: Consumer<Dog> = AnimalConsumer() // Works fine

Why Use in?

  • Again it ensures type safety.
  • Used when a class only takes in values of type T.
  • Common in function parameters, like Comparator<T>.

Example:

Kotlin
fun addDogsToShelter(consumer: Consumer<Dog>) {
    consumer.consume(Dog())
}

Since Consumer<Animal> is a supertype of Consumer<Dog>, this works perfectly.

Invariance (Default Behavior: No in or out)

By default, generics in Kotlin are invariant, meaning Box<Dog> is NOT a subtype or supertype of Box<Animal>, even though Dog is a subtype of Animal. Means, they do not support substitution for subtypes or supertypes.

Kotlin
class Box<T>(val item: T)

val dogBox: Box<Dog> = Box(Dog())
val animalBox: Box<Animal> = dogBox // Error: Type mismatch

Why?

Without explicit variance, Kotlin prevents unsafe assignments. If variance is not declared, Kotlin assumes that T can be both produced and consumed, making it unsafe to assume subtyping.

Star Projection (*) – When Type is Unknown

Sometimes, you don’t know the exact type parameter but still need to work with a generic class. Kotlin provides star projection (*) to handle such cases.

Example:

Kotlin
fun printList(list: List<*>) {
    for (item in list) {
        println(item) // Treated as Any?
    }
}

A List<*> means it could be List<Any>, List<String>, List<Int>, etc., but we cannot modify it because we don’t know the exact type.

Best Practices for Kotlin Variance

  • Use out when the type is only produced (e.g., List<T>).
  • Use in when the type is only consumed (e.g., Comparator<T>).
  • Keep generics invariant unless variance is required.
  • Use star projections (*) when you don’t know the type but need read access.

Conclusion

Understanding Kotlin Variance is crucial for writing flexible and type-safe code. Covariance (out) is used for producers, contravariance (in) for consumers, and invariance when both roles exist. By mastering Kotlin Variance concepts, you can work effectively with generics and subtyping in Kotlin.

Kotlin Coroutines

Kotlin Coroutines in Android: The Ultimate Guide to Asynchronous Programming

If you’ve ever dealt with asynchronous programming in Android, you know it can get messy fast. Callback hell, thread management, and performance issues make it a nightmare. That’s where Kotlin Coroutines come in. Coroutines provide a simple, structured way to handle background tasks without blocking the main thread, making your code more readable and efficient.

In this guide, we’ll break down Kotlin Coroutines, how they work, and how to use them effectively in Android development.

What Are Kotlin Coroutines?

Kotlin Coroutines are lightweight threads that allow you to write asynchronous code sequentially, making it easier to read and maintain. Unlike traditional threads, coroutines are not bound to a specific thread. Instead, they are managed by Kotlin’s Coroutine framework, which optimizes execution and minimizes resource consumption.

Basically, coroutines in Kotlin provide a way to perform asynchronous tasks without blocking the main thread. They are lightweight, suspendable functions that allow execution to be paused and resumed efficiently, making them ideal for operations like network calls, database queries, and intensive computations.

Setting Up Coroutines in Android

To use Kotlin Coroutines in your Android project, add the necessary dependencies in your build.gradle (Module) file:

Kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") // use latest 
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") // use latest

Sync your project, and you’re ready to use coroutines..!

Key Concepts of Kotlin Coroutines

1. Suspend Functions

A suspend function is a special type of function that can be paused and resumed without blocking the thread. It is marked with the suspend keyword and can only be called from another suspend function or a coroutine.

Kotlin
suspend fun fetchData(): String {
    delay(1000) // Simulate network delay
    return "Data fetched successfully"
}

Here, delay(1000) suspends the function for 1 second without blocking the thread.

2. Coroutine Scopes

A coroutine scope defines the lifecycle of coroutines. When a scope is canceled, all coroutines inside it are also canceled.

Common Scopes:

  • GlobalScope: A global, long-living scope for coroutines, means lives as long as the app process.
  • CoroutineScope: A general scope to manage coroutine execution.
  • viewModelScope: Lifecycle-aware scope for Android ViewModels, it’s tied to the ViewModel’s lifecycle.
  • lifecycleScope: Tied to Android lifecycle components, like an activity or fragment lifecycle.

3. Coroutine Builders

Kotlin provides built-in coroutine builders to create coroutines:

launch (Fire-and-forget)

launch starts a coroutine without returning a result. It is typically used for fire-and-forget tasks. However, it returns a Job object, which can be used to manage its lifecycle (e.g., cancellation or waiting for completion).

Kotlin
CoroutineScope(Dispatchers.IO).launch {
    val data = fetchData()
    println("Data: $data")
}

async and await (Return a result)

async is used when you need a result. It returns a Deferred<T> object that you can await to get the result.

Kotlin
CoroutineScope(Dispatchers.IO).launch {
    val result = async { fetchData() }
    println("Data: ${result.await()}")
}

Use async when you need to perform parallel computations.

4. Coroutine Dispatchers

Coroutine dispatchers determine which thread a coroutine runs on.

  • Dispatchers.Main → Runs on the main UI thread (for UI updates).
  • Dispatchers.IO → Optimized for disk and network operations.
  • Dispatchers.Default → Used for CPU-intensive tasks.
  • Dispatchers.Unconfined → Starts in the caller thread but can switch threads.

Switching Between Dispatchers

Sometimes, you may need to switch between dispatchers within a coroutine. Use withContext() to change dispatchers efficiently.

Kotlin
suspend fun fetchDataAndUpdateUI() {
    val data = withContext(Dispatchers.IO) {
        fetchDataFromNetwork()
    }
    withContext(Dispatchers.Main) {
        println("Updating UI with data: $data")
    }
}

5. Structured Concurrency

Structured concurrency means that coroutines are bound to a specific scope. When a scope is canceled, all coroutines inside it are also canceled. This approach ensures that coroutines do not outlive their intended lifecycle, preventing memory leaks and dangling tasks.

Parent-Child Relationship in Coroutines

One of the key features of structured concurrency in coroutines is the parent-child relationship. When a parent coroutine is canceled, all of its child coroutines are automatically canceled.

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        launch {
            delay(1000)
            println("Child Coroutine - Should not run if parent is canceled")
        }
    }
    
    delay(500) // Give some time for coroutine to start
    job.cancelAndJoin() // Cancel parent coroutine and wait for completion
    println("Parent Coroutine Canceled")
    delay(1500) // Allow time to observe behavior (not necessary)
}

///////// OUTPUT //////////
Parent Coroutine Canceled

The child coroutine should never print because it gets canceled before it can execute println()

SupervisorScope: Handling Failures Gracefully

A common issue in coroutines is that if one child coroutine fails, it cancels the entire scope. To handle failures gracefully, Kotlin provides supervisorScope.

Kotlin
suspend fun main() = coroutineScope {
    supervisorScope {
        launch {
            delay(500L)
            println("First coroutine running")
        }
        launch {
            delay(300L)
            throw RuntimeException("Error in second coroutine")
        }
    }
    println("Scope continues running")
}


///////////// OUTPUT //////////////////////
Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: Error in second coroutine
First coroutine running
Scope continues running

Here,

  • supervisorScope ensures that one coroutine’s failure does not cancel others.
  • The first coroutine completes successfully even if the second one fails.
  • Without supervisorScope, the whole scope would be canceled when an error occurs.

Unlike coroutineScope, supervisorScope ensures that one failing child does not cancel the entire scope.

6. Exception Handling in 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.

Conclusion

Kotlin Coroutines simplify asynchronous programming in Android, making it more readable, efficient, and structured. By understanding coroutine scopes, dispatchers, and error handling, you can build robust Android applications with minimal effort.

Start using Kotlin Coroutines today, and say goodbye to callback hell..!

Kotlin Object Thread-Safe

Is a Kotlin Object Thread-Safe Without Extra Synchronization?

Kotlin provides a powerful object declaration that simplifies singleton creation. But an important question arises: Is a Kotlin object completely thread-safe without additional synchronization? The answer is nuanced. While the initialization of an object is thread-safe, the state inside the object may not be. This post dives deep into Kotlin object thread safety, potential pitfalls, and how to make objects fully safe for concurrent access.

Why Are Kotlin Objects Considered Thread-Safe?

Kotlin object declarations follow the JVM class loading mechanism, which ensures that an object is initialized only once, even in a multi-threaded environment. This guarantees that the creation of the object itself is always thread-safe.

Kotlin
object Singleton {
    val someValue = 42  // Immutable, safe to access from multiple threads
}
  • Here, Singleton is initialized only once.
  • The property someValue is immutable, making it inherently thread-safe.

If all properties inside the object are immutable (val), you don’t need to worry about thread safety.

When Is a Kotlin Object NOT Thread-Safe?

Although the initialization of the object is safe, modifying mutable state inside the object is NOT automatically thread-safe. This is because multiple threads can access and modify the state at the same time, leading to race conditions.

Kotlin
import kotlin.concurrent.thread

object Counter {
    var count = 0  // Mutable state, not thread-safe

    fun increment() {
        count++  // Not atomic, can lead to race conditions
    }
}

fun main() {
    val threads = List(100) {
        thread {
            repeat(1000) {
                Counter.increment()
            }
        }
    }
    threads.forEach { it.join() }
    println("Final count: ${Counter.count}")
}

What’s wrong here?

  • count++ is not an atomic operation.
  • If multiple threads call increment() simultaneously, they might overwrite each other’s updates, leading to incorrect results.

How to Make a Kotlin Object Fully Thread-Safe?

Solution 1: Using synchronized Keyword

One way to make the object thread-safe is by synchronizing access to mutable state using @Synchronized.

Kotlin
object Counter {
    private var count = 0

    @Synchronized
    fun increment() {
        count++
    }

    @Synchronized
    fun getCount(): Int = count
}

Thread-safe: Only one thread can modify count at a time. 

Performance overhead: synchronized introduces blocking, which might slow down performance under high concurrency.

Solution 2: Using AtomicInteger (Better Performance)

A more efficient alternative is using AtomicInteger, which provides lock-free thread safety.

Kotlin
import java.util.concurrent.atomic.AtomicInteger

object Counter {
    private val count = AtomicInteger(0)

    fun increment() {
        count.incrementAndGet()
    }

    fun getCount(): Int = count.get()
}

Thread-safe: AtomicInteger handles atomic updates internally. 

Better performance: Avoids blocking, making it more efficient under high concurrency.

Solution 3: Using ConcurrentHashMap or ConcurrentLinkedQueue (For Collections)

If your object manages a collection, use thread-safe collections from java.util.concurrent.

Kotlin
import java.util.concurrent.ConcurrentHashMap

object SafeStorage {
    private val data = ConcurrentHashMap<String, String>()

    fun put(key: String, value: String) {
        data[key] = value
    }

    fun get(key: String): String? = data[key]
}

Thread-safe: Uses a concurrent data structure. 

No need for explicit synchronization.

Conclusion

A Kotlin object is always initialized in a thread-safe manner due to JVM class loading mechanisms. However, mutable state inside the object is NOT automatically thread-safe

To ensure full thread safety:

  • Use @Synchronized for simple synchronization.
  • Use AtomicInteger for atomic operations.
  • Use ConcurrentHashMap or ConcurrentLinkedQueue for collections. 

For optimal performance, prefer lock-free solutions like atomic variables or concurrent collections.

By understanding these nuances, you can confidently write thread-safe Kotlin objects that perform well in multi-threaded environments.

Structured Concurrency in Coroutines

The Power of Structured Concurrency: How Kotlin Keeps Coroutines Manageable

Kotlin’s coroutines have revolutionized asynchronous programming on the JVM. They make concurrent operations simpler and more efficient. However, without proper control, coroutines can become chaotic, leading to memory leaks, unhandled errors, and debugging nightmares. This is where structured concurrency in coroutines comes to the rescue.

Structured concurrency ensures that coroutines are launched, supervised, and cleaned up properly. It keeps your code maintainable, predictable, and safe. In this post, we’ll explore how structured concurrency works in Kotlin, why it matters, and how you can implement it effectively.

What is Structured Concurrency in Coroutines?

Structured concurrency in coroutines is a principle that ensures all coroutines launched in an application have a well-defined scope and lifecycle. Instead of launching coroutines in an unstructured, free-floating manner, structured concurrency ensures they:

  • Are tied to a specific scope.
  • Get automatically canceled when their parent scope is canceled.
  • Avoid memory leaks by ensuring proper cleanup.
  • Provide predictable execution and error handling.

Kotlin achieves this by leveraging CoroutineScope, which acts as a container for coroutines.

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("Coroutine completed!")
    }
    println("Main function ends")
}

Here,

  • runBlocking creates a coroutine scope and blocks execution until all coroutines inside it complete.
  • launch starts a new coroutine inside runBlocking.
  • The coroutine delays for 1 second before printing the message.
  • The main function waits for all coroutines (here only one) to finish before exiting.

Without structured concurrency, coroutines would run freely, possibly outliving their parent functions. This can lead to unpredictable behavior.

Kotlin enforces structured concurrency using CoroutineScope, which defines the lifecycle of coroutines. Every coroutine must be launched within a scope, ensuring proper supervision and cleanup.

Implementing Structured Concurrency in Kotlin

Using CoroutineScope

Every coroutine in Kotlin should be launched within a CoroutineScope. The CoroutineScope ensures that all launched coroutines get canceled when the scope is canceled.

Example using CoroutineScope:

Kotlin
class MyClass {
    private val scope = CoroutineScope(Dispatchers.IO)

    fun fetchData() {
        scope.launch {
            val data = fetchFromNetwork()
            println("Data received: $data")
        }
    }

    fun cleanup() {
        scope.cancel() // Cancels all coroutines within this scope
    }

    private suspend fun fetchFromNetwork(): String {
        delay(1000L)
        return "Sample Data"
    }
}

Here,

  • CoroutineScope(Dispatchers.IO) creates a scope for background operations.
  • fetchData launches a coroutine within the scope.
  • cleanup cancels the scope, stopping all running coroutines inside it.

This ensures that all coroutines are properly managed and do not outlive their intended use.

Parent-Child Relationship in Coroutines

One of the key features of structured concurrency in coroutines is the parent-child relationship. When a parent coroutine is canceled, all of its child coroutines are automatically canceled.

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        launch {
            delay(1000)
            println("Child Coroutine - Should not run if parent is canceled")
        }
    }
    
    delay(500) // Give some time for coroutine to start
    job.cancelAndJoin() // Cancel parent coroutine and wait for completion
    println("Parent Coroutine Canceled")
    delay(1500) // Allow time to observe behavior (not necessary)
}


///////// OUTPUT //////////

Parent Coroutine Canceled

The child coroutine should never print because it gets canceled before it can execute println()

SupervisorScope: Handling Failures Gracefully

A common issue in coroutines is that if one child coroutine fails, it cancels the entire scope. To handle failures gracefully, Kotlin provides supervisorScope.

Kotlin
suspend fun main() = coroutineScope {
    supervisorScope {
        launch {
            delay(500L)
            println("First coroutine running")
        }
        launch {
            delay(300L)
            throw RuntimeException("Error in second coroutine")
        }
    }
    println("Scope continues running")
}


///////////// OUTPUT //////////////////////

Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: Error in second coroutine
First coroutine running
Scope continues running

Here,

  • supervisorScope ensures that one coroutine’s failure does not cancel others.
  • The first coroutine completes successfully even if the second one fails.
  • Without supervisorScope, the whole scope would be canceled when an error occurs.

Unlike coroutineScope, supervisorScope ensures that one failing child does not cancel the entire scope.

Benefits of Structured Concurrency

Using structured concurrency in coroutines offers several benefits:

  1. Automatic Cleanup: When the parent coroutine is canceled, all child coroutines are also canceled, preventing leaks.
  2. Error Propagation: If a child coroutine fails, the exception propagates to the parent, ensuring proper handling.
  3. Scoped Execution: Coroutines only exist within their intended scope, making them predictable and manageable.
  4. Better Debugging: With clear parent-child relationships, debugging coroutine issues becomes easier.

Best Practices for Structured Concurrency in Coroutines

  1. Use CoroutineScope for proper lifecycle management.
  2. Avoid GlobalScope unless absolutely necessary.
  3. Use supervisorScope to prevent one failure from affecting all coroutines.
  4. Always cancel unused coroutines to free up resources.
  5. Handle exceptions properly to prevent crashes.

Conclusion

Conclusion

Kotlin’s structured concurrency in coroutines ensures that coroutines are properly managed, reducing memory leaks and making concurrent programming more predictable. By using CoroutineScope, enforcing the parent-child relationship, and leveraging supervisorScope when necessary, you can build robust and efficient Kotlin applications.

Mastering structured concurrency will not only make your code more maintainable but also help you avoid common pitfalls of asynchronous programming. Start applying these best practices today and take full advantage of Kotlin’s coroutine capabilities..!

Constructor Parameters in Kotlin Variance

The Role of Constructor Parameters in Kotlin Variance Explained

Kotlin’s type system offers robust features for managing generics, including the concepts of covariance and contravariance. A common question among developers is how constructor parameters influence variance in Kotlin. Let’s explore this topic in a straightforward and approachable manner.

Generics and Variance in Kotlin

Before diving into the Role of Constructor Parameters in Kotlin Variance, it’s essential to grasp the basics of generics and variance:

Generics allow classes and functions to operate on different data types while maintaining type safety.

Variance defines how subtyping between more complex types relates to subtyping between their component types. In Kotlin, this is managed using the in and out modifiers.

  • The out modifier indicates that a type parameter is covariant, meaning the class can produce values of that type but not consume them.
  • The in modifier denotes contravariance, where the class can consume values of that type but not produce them.

The Role of Constructor Parameters

In Kotlin, constructor parameters are not considered to be in the “in” or “out” position when it comes to variance. This means that even if a type parameter is declared as “out,” you can still use it in a constructor parameter declaration without any restrictions.

For example:

Kotlin
class Herd<out T: Animal>(vararg animals: T) { ... }

The type parameter T is declared as “out,” but it can still be used in the constructor parameter vararg animals: T without any issues. The variance protection is not applicable to the constructor because it is not a method that can be called later, so there are no potentially dangerous method calls that need to be restricted.

However, if you use the val or var keyword with a constructor parameter, it declares a property with a getter and setter (if the property is mutable). In this case, the type parameter T is used in the “out” position for a read-only property and in both “out” and “in” positions for a mutable property.

For example:

Kotlin
class Herd<T: Animal>(var leadAnimal: T, vararg animals: T) { ... }

Here, the type parameter T cannot be marked as “out” because the class contains a setter for the leadAnimal property, which uses T in the “in” position. The presence of a setter makes it necessary to consider both “out” and “in” positions for the type parameter.

It’s important to note that the position rules for variance in Kotlin only apply to the externally visible API of a class, such as public, protected, and internal members. Parameters of private methods are not subject to the “in” or “out” position rules. The variance rules are in place to protect a class from misuse by external clients and do not affect the implementation of the class itself.

For instance:

Kotlin
class Herd<out T: Animal>(private var leadAnimal: T, vararg animals: T) { ... }

In this case, the Herd class can safely be made covariant on T because the leadAnimal property has been made private. The private visibility means that the property is not accessible from external clients, so the variance rules for the public API do not apply.

Conclusion

In Kotlin, constructor parameters are neutral regarding variance. This neutrality ensures that you can use generic types in constructors without affecting the covariant or contravariant nature of your classes. Understanding this aspect of Kotlin’s type system enables you to write more robust and flexible generic classes.

By keeping constructor parameters and variance separate, Kotlin provides a type-safe environment that supports both flexibility and clarity in generic programming.

error: Content is protected !!