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.
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.*funmain() = runBlocking {// Create a MutableSharedFlow with a buffer capacity of 2val 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 emittingdelay(100) sharedFlow.emit(10) sharedFlow.emit(20)delay(500) job1.cancel() job2.cancel()}// OutputCollector 1 received: 10Collector 2 received: 10Collector 1 received: 20Collector 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.
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 AppProject 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.
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."}
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..!
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:
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.
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 :
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
Stronger Type Safety — Prevents incorrect type assignments.
More Readable Code — Clearly defines how a generic class is used.
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.
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.
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 <reifiedT> someFunction() { // Error: Type parameter T cannot be reifiedprintln(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:
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
classMyClass<reifiedT> { // This is not allowed!funprintType() {println("The type is: ${T::class.simpleName}") }}
Error:
Kotlin
Reified type parameters are only allowed forinline functions.
Workaround: Instead of using a class-level generic with reified, define an inline function inside the class:
Kotlin
classMyClass<T> {inlinefun <reifiedT> 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
classExample<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
classExample<T>(privateval clazz: Class<T>) {fungetType(): Class<T> = clazz}funmain() {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
inlinefun <reifiedT> createInstance(): T {returnT() // 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
inlinefun <reifiedT> createInstance(factory: () -> T): T {returnfactory()}
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.
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 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
objectDatabase {val name = "MyDatabase"funconnect() {println("Connected to $name") }}funmain() { 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.
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.
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:
Feature
Object Expression
Object Declaration
Instantiation
Every time used
Only once (Singleton)
Named?
No
Yes
Use case
One-time use
Global 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 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
openclassAnimalclassDog : 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
interfaceProducer<outT> {funproduce(): T}
This means that Producer<Dog> can be used where Producer<Animal> is expected:
Kotlin
classDogProducer : Producer<Dog> {overridefunproduce(): 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.
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
interfaceConsumer<inT> {funconsume(item: T)}
This means that Consumer<Animal> can be used where Consumer<Dog> is expected:
Kotlin
classAnimalConsumer : Consumer<Animal> {overridefunconsume(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>.
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.
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
funprintList(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.
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.
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).
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.*funmain() = 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 completionprintln("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
suspendfunmain() = coroutineScope {supervisorScope {launch {delay(500L)println("First coroutine running") }launch {delay(300L)throwRuntimeException("Error in second coroutine") } }println("Scope continues running")}///////////// OUTPUT //////////////////////Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: ErrorinsecondcoroutineFirst coroutine runningScope 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:
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.
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 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
objectSingleton {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.threadobjectCounter {var count = 0// Mutable state, not thread-safefunincrement() { count++ // Not atomic, can lead to race conditions }}funmain() {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.
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.
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.*funmain() = 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
classMyClass {privateval scope = CoroutineScope(Dispatchers.IO)funfetchData() { scope.launch {valdata = fetchFromNetwork()println("Data received: $data") } }funcleanup() { scope.cancel() // Cancels all coroutines within this scope }privatesuspendfunfetchFromNetwork(): 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.*funmain() = 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 completionprintln("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
suspendfunmain() = coroutineScope {supervisorScope {launch {delay(500L)println("First coroutine running") }launch {delay(300L)throwRuntimeException("Error in second coroutine") } }println("Scope continues running")}///////////// OUTPUT //////////////////////Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: ErrorinsecondcoroutineFirst coroutine runningScope 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:
Automatic Cleanup: When the parent coroutine is canceled, all child coroutines are also canceled, preventing leaks.
Error Propagation: If a child coroutine fails, the exception propagates to the parent, ensuring proper handling.
Scoped Execution: Coroutines only exist within their intended scope, making them predictable and manageable.
Best Practices for Structured Concurrency in Coroutines
Use CoroutineScope for proper lifecycle management.
Avoid GlobalScope unless absolutely necessary.
Use supervisorScope to prevent one failure from affecting all coroutines.
Always cancel unused coroutines to free up resources.
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..!
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 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.
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.
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.
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.