Kotlin

Mastering Kotlin Coroutines

Mastering Kotlin Coroutines: A Deep Dive into Concurrency and Threading

Kotlin Coroutines have revolutionized how we handle asynchronous programming in Android and server-side applications. They provide a structured and efficient way to manage background tasks without blocking threads. This in-depth guide explores Coroutines, their execution model, dispatchers, structured concurrency, and best practices with real-world examples. Why Kotlin Coroutines? Traditionally, Java developers relied on Threads, Executors,...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Function Type Variance

How Covariance and Contravariance Work in Kotlin’s Function Types

Kotlin’s function type variance is an important concept that helps developers understand how types behave when dealing with function inputs and outputs. If you’ve ever wondered why function parameters and return types follow different variance rules, this post will clear things up..!

Understanding Variance in Kotlin

Variance determines how subtyping relationships work in a type hierarchy. There are two key types of variance in Kotlin:

  1. Covariance (Out-projected types): A subtype can replace a supertype when reading values.
  2. Contravariance (In-projected types): A supertype can replace a subtype when writing values.

Function Types and Variance

Kotlin functions can be represented as types, for example:

Kotlin
val func: (String) -> Int = { it.length }

Here, func is a function type that takes a String as input and returns an Int. Function types in Kotlin have two parts:

  1. Parameter type(s) — What the function takes in.
  2. Return type — What the function returns.

The variance in function types applies differently to parameters and return types.

  • Return types are covariant (out).
  • Parameter types are contravariant (in).

Now, let’s explore how these apply to Kotlin’s function type variance.

Covariance and Contravariance in Kotlin’s Function Types

In Kotlin, a class or interface can be covariant on one type parameter and contravariant on another. One of the classic examples of this is the Function interface. Let’s take a look at the declaration of the Function1 interface, which represents a one-parameter function:

Kotlin
interface Function1<in P, out R> {
    operator fun invoke(p: P): R
}

To make the notation more readable, Kotlin provides an alternative syntax (P) -> R to represent Function1<P, R>. In this syntax, you’ll notice that P (the parameter type) is used only in the in position and is marked with the in keyword, while R (the return type) is used only in the out position and is marked with the out keyword.

This means that the subtyping relationship for the function type is reversed for the first type argument (P) and preserved for the second type argument (R).

For example, let’s say you have a higher-order function called enumerateCats that accepts a lambda function taking a Cat parameter and returning a Number:

Kotlin
fun enumerateCats(f: (Cat) -> Number) { ... }

Now, suppose you have a function called getIndex defined in the Animal class that returns an Int. You can pass Animal::getIndex as an argument to enumerateCats:

Kotlin
fun Animal.getIndex(): Int = ...
enumerateCats(Animal::getIndex)       // This code is legal in Kotlin. Animal is a supertype of Cat, and Int is a subtype of Number

In this case, the Animal::getIndex function is accepted because Animal is a supertype of Cat, and Int is a subtype of Number, the function type’s subtyping relationship allows it.

The function (T) -> R is contravariant on its argument and covariant on its return type

This illustration demonstrates how subtyping works for function types. The arrows indicate the subtyping relationship.

Function Type Variance in Kotlin’s Type System

As you now know, when working with function types in Kotlin, variance is explicitly defined:

Kotlin
interface Function1<in P, out R> {
    fun invoke(param: P): R
}
  • P (parameter) is contravariant (in)
  • R (return type) is covariant (out)

This aligns with the natural logic of function type variance.

Conclusion

So in summary,

  • Covariance (out) applies to return types – A function returning a subtype can be assigned to a function returning a supertype.
  • Contravariance (in) applies to parameter types – A function taking a supertype can be assigned to a function taking a subtype.
  • Kotlin enforces these rules to ensure type safety and prevent runtime errors.

Understanding Kotlin’s Function Type Variance helps write flexible, reusable, and type-safe code, especially when designing APIs, lambda functions, and higher-order functions.

Kotlin Contravariance

Kotlin Contravariance Explained: Understanding Reversed Subtyping

Kotlin has a robust type system, but one of the trickiest concepts to grasp is contravariance. If you’ve ever scratched your head wondering how contravariance works and why it’s useful, you’re in the right place. This blog will break it down in simple terms with examples so you can fully understand Kotlin contravariance and use it effectively in your projects.

Understanding Variance in Kotlin

Before diving into Kotlin contravariance, let’s briefly cover variance. Variance determines how subtyping relationships between generic types work. Kotlin has three main types:

  1. Invariance (T) – No subtyping allowed.
  2. Covariance (out T) – Allows a subtype to be returned but not consumed.
  3. Contravariance (in T) – Allows a supertype to be accepted as input.

Contravariance is often misunderstood because it involves a reversed subtyping rule. Let’s break it down.

Contravariance: reversed subtyping relation

Contravariance is the opposite of covariance and it can be understood as a mirror image of covariance. When a class is contravariant, the subtyping relationship between its type arguments is the reverse of the subtyping relationship between the classes themselves.

To illustrate this concept, let’s consider the example of the Comparator interface. This interface has a single method called compare, which takes two objects and compares them:

Kotlin
interface Comparator<in T> {
    fun compare(e1: T, e2: T): Int { ... }
}

In this case, you’ll notice that the compare method only consumes values of type T. This means that the type parameter T is used in “in” positions only, indicating that it is a contravariant type. To indicate contravariance, the “in” keyword is placed before the declaration of T.

A comparator defined for values of a certain type can, of course, compare the values of any subtype of that type. For example, if you have a Comparator, you can use it to compare values of any specific type.

Kotlin
val anyComparator = Comparator<Any> { e1, e2 -> e1.hashCode() - e2.hashCode() }

val strings: List<String> = listOf("abc","xyz")
strings.sortedWith(anyComparator)     // You can use the comparator for any objects to compare specific objects, such as strings.

Here, the sortedWith function expects a Comparator (a comparator that can compare strings), and it’s safe to pass one that can compare more general types. If you need to perform comparisons on objects of a certain type, you can use a comparator that handles either that type or any of its supertypes. This means Comparator<Any> is a subtype of Comparator<String>, where Any is a supertype of String. The subtyping relation between comparators for two different types goes in the opposite direction of the subtyping relation between those types.

What is contravariance?

A class that is contravariant on the type parameter is a generic class (let’s consider Consumer<T> as an example) for which the following holds: Consumer<A> is a subtype of Consumer<B> if B is a subtype of A. The type arguments A and B changed places, so we say the subtyping is reversed. For example, Consumer<Animal> is a subtype of Consumer<Cat>.

In simple words, contravariance in Kotlin means that the subtyping relationship between two generic types is reversed compared to the normal inheritance hierarchy. If B is a subtype of A, then a generic class or interface that is contravariant on its type parameter T will have the relationship ClassName<A> is a subtype of ClassName<B>.

For a covariant type Producer, the subtyping is preserved, but for a contravariant type Consumer, the subtyping is reversed

Here, we see the difference between the subtyping relation for classes that are covariant and contravariant on a type parameter. You can see that for the Producer class, the subtyping relation replicates the subtyping relation for its type arguments, whereas for the Consumer class, the relation is reversed.

Covariant, contravariant, and invariant classes

The “in” keyword means values of the corresponding type are passed in to methods of this class and consumed by those methods. Similar to the covariant case, constraining use of the type parameter leads to the specific subtyping relation. The “in” keyword on the type parameter T means the subtyping is reversed and T can be used only in “in” positions.

When to Use Contravariance

Contravariance is useful when designing APIs that consume values. It is commonly used in:

  • Event handlers
  • Callbacks and listeners
  • Comparator implementations (Comparator<in T>)

Conclusion

Kotlin contravariance can be tricky at first, but once you understand its reversed subtyping rule, it becomes a powerful tool for designing flexible and reusable APIs. By using the in keyword, you allow broader types to be used while ensuring type safety.

Next time you design a consumer-style interface, consider whether Kotlin contravariance could make your code more robust.

fold and filter Functions in Kotlin

Mastering fold and filter Functions in Kotlin: A Comprehensive Guide

Kotlin provides powerful collection functions that make data manipulation intuitive and efficient. Among them, fold and filter stand out as essential tools for transforming and processing collections. In this guide, we’ll explore these functions in depth, providing detailed explanations and practical examples to help you leverage their full potential. Understanding the fold Function in Kotlin The...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Coroutine Dispatcher in Kotlin

Coroutine Dispatchers in Kotlin: When to Use Dispatchers.IO, Main, Default, and Unconfined

Kotlin Coroutines make asynchronous programming simpler and more efficient, but choosing the right dispatcher is crucial for performance and responsiveness. In this guide, we’ll explore Coroutine Dispatchers in Kotlin, focusing on Dispatchers.IO, Dispatchers.Main, Dispatchers.Default, and Dispatchers.Unconfined—when to use each, how they work, and best practices. What Are Coroutine Dispatchers in Kotlin? Coroutine Dispatchers determine the thread...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
kotlin flow

Kotlin Flow: A Deep Dive into Reactive Streams in Kotlin

In modern Android and Kotlin development, handling asynchronous data streams efficiently is crucial. Whether you’re working with API responses, user input events, or real-time updates, Kotlin Flow provides a powerful, coroutine-based solution. In this guide, we’ll explore Kotlin Flow in-depth, covering its concepts, operators, builders, real-world use cases, and best practices. What is Kotlin Flow? Kotlin...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Coroutine Scopes

Why Coroutine Scopes Matter: A Deep Dive into Kotlin’s Concurrency Model

Kotlin’s coroutines have revolutionized asynchronous programming by making concurrency more manageable and readable. But to truly harness their power, understanding Coroutine Scopes is essential. In this guide, we’ll break down what Coroutine Scopes are, why they matter, and how they fit into Kotlin’s concurrency model. What Are Coroutine Scopes? A Coroutine Scope defines the lifecycle...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Use-Site Variance

Use-Site Variance in Kotlin Explained: When & How It Works

Kotlin is a powerful and flexible programming language that introduces many advanced features to make development easier. One such feature is Use-Site Variance in Kotlin, which helps handle type variance effectively in generic programming. If you’ve ever struggled with understanding variance in Kotlin, this guide will break it down in a simple, easy-to-understand way.

What is Use-Site Variance in Kotlin?

In Kotlin, variance defines how different types relate to each other in a type hierarchy when dealing with generics. Generics allow you to create flexible and reusable code, but without proper variance handling, type safety issues may arise.

Kotlin provides two types of variance:

  1. Declaration-Site Variance — Defined at the class level using out (covariance) and in (contravariance).
  2. Use-Site Variance — Applied at the point where a generic class is used, rather than where it is declared.

Use-Site Variance is particularly useful when you don’t have control over the generic class declaration but still need to enforce type variance.

When to Use Use-Site Variance in Kotlin?

Use-Site Variance in Kotlin is useful in scenarios where:

  • You are working with a generic class that doesn’t specify variance at the declaration level.
  • You need to ensure type safety when passing a generic object to a function.
  • You want to restrict read and write operations based on the expected type.

Understanding when to use it is important because incorrect usage may lead to type mismatches and compilation errors.

BTW, How does use-site variance work in Kotlin?

Kotlin supports use-site variance, you can specify variance at the use site, which means you can indicate the variance for a specific occurrence of a type parameter, even if it can’t be declared as covariant or contravariant in the class declaration. Let’s break down the concepts and see how use-site works.

In Kotlin, many interfaces, like MutableList, are not covariant or contravariant by default because they can both produce and consume values of the types specified by their type parameters. However, in certain situations, a variable of that type may be used only as a producer or only as a consumer.

Consider the function copyData that copies elements from one collection to another:

Kotlin
fun <T> copyData(source: MutableList<T>, destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}

In this function, both the source and destination collections have an invariant type. However, the source collection is only used for reading, and the destination collection is only used for writing. In this case, the element types of the collections don’t need to match exactly.

To make this function work with lists of different types, you can introduce a second generic parameter:

Kotlin
fun <T : R, R> copyData(source: MutableList<T>, destination: MutableList<R>) {
    for (item in source) {
        destination.add(item)
    }
}

In this modified version, you declare two generic parameters representing the element types in the source and destination lists. The source element type (T) should be a subtype of the elements in the destination list (R).

However, Kotlin provides a more elegant way to express this using use-site variance. If the implementation of a function only calls methods that have the type parameter in the “out” position (as a producer) or only in the “in” position (as a consumer), you can add variance modifiers to the particular usages of the type parameter in the function definition.

For example, you can modify the copyData function as follows:

Kotlin
fun <T> copyData(source: MutableList<out T>, destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}

In this version, you specify the out modifier for the source parameter, which means it’s a projected (restricted) MutableList. You can only call methods that return the generic type parameter (T) or use it in the “out” position. The compiler prohibits calling methods where the type parameter is used as an argument (“in” position).

Here’s an example usage:

Kotlin
val ints = mutableListOf(1, 2, 3)
val anyItems = mutableListOf<Any>()
copyData(ints, anyItems)
println(anyItems) // [1, 2, 3]

When using use-site variance in Kotlin, there are limitations on the methods that can be called on a projected type. If you are using a projected type, you may not be able to call certain methods that require the type parameter to be used as an argument (“in” position) :

Kotlin
val list: MutableList<out Number> = ...
list.add(42) // Error: Out-projected type 'MutableList<out Number>' prohibits the use of 'fun add(element: E): Boolean'

Here, list is declared as a MutableList<out Number>, which is an out-projected type. The out projection restricts the type parameter Number to only be used in the “out” position, meaning it can only be used as a return type or read from. You cannot call the add method because it requires the type parameter to be used as an argument (“in” position).

If you need to call methods that are prohibited by the projection, you should use a regular type instead of a projection. In this case, you can use MutableList<Number> instead of MutableList<out Number>. By using the regular type, you can access all the methods available for that type.

Regarding the concept of using the in modifier, it indicates that in a particular location, the corresponding value acts as a consumer, and the type parameter can be substituted with any of its supertypes. This is similar to the contravariant position in Java’s bounded wildcards.

For example, the copyData function can be rewritten using an in-projection:

Kotlin
fun <T> copyData(source: MutableList<T>, destination: MutableList<in T>) {
    for (item in source) {
        destination.add(item)
    }
}

In this version, the destination parameter is projected with the in modifier, indicating that it can consume elements of type T or any of its supertypes. This allows you to copy elements from the source list to a destination list with a broader type.

It’s important to note that use-site variance declarations in Kotlin correspond directly to Java’s bounded wildcards. MutableList<out T> in Kotlin is equivalent to MutableList<? extends T> in Java, while the in-projected MutableList<in T> corresponds to Java’s MutableList<? super T>.

Use-site projections in Kotlin can help widen the range of acceptable types and provide more flexibility when working with generic types, without the need for separate covariant or contravariant interfaces.

Conclusion

Use-Site Variance in Kotlin is a powerful feature that ensures type safety while working with generics. By using out when you only need to retrieve values and in when you only need to pass values, you can effectively manage type variance without modifying class declarations.

Understanding how and when to use Use-Site Variance in Kotlin can help you write cleaner, more flexible, and safer generic code. Start experimenting with it in your Kotlin projects and see how it simplifies handling type variance..!

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..!

error: Content is protected !!