🕹️ Concurrency in Kotlin

Gorkem KARA
4 min readAug 5, 2024

--

Turkish: https://gorkemkara.medium.com/%EF%B8%8F-kotlinde-e%C5%9Fzamanl%C4%B1l%C4%B1k-concurrency-7cca35a7368f

Concurrency is the ability of a program to run multiple threads at the same time. This is important for handling large and complex operations more efficiently. Kotlin provides various tools for managing concurrency. In this article, we will explore concurrency in Kotlin and how to manage it using clean code standards.

🎯 Why Should We Use Concurrency?

Increased Efficiency: Running multiple threads at the same time allows operations to complete faster.

User Experience: Long-running operations do not block the user interface, providing a better user experience.

Resource Utilization: It allows more efficient use of system resources.

🔧 Implementing Concurrency in Kotlin

Below is an example of how concurrency can be implemented in Kotlin. In this example, we will fetch data from a website concurrently.

Bad Example: Fetching Data Without Concurrency

fun fetchData() {
val data1 = fetchFromServer("https://api.example.com/data1")
val data2 = fetchFromServer("https://api.example.com/data2")
println("Data1: $data1")
println("Data2: $data2")
}

fun fetchFromServer(url: String): String {
// Simulated network delay
Thread.sleep(1000)
return "Response from $url"
}

In this example, data fetching operations are performed sequentially, which increases the total time and decreases efficiency.

Good Example: Fetching Data with Concurrency

import kotlinx.coroutines.*

fun main() = runBlocking {
val data1 = async { fetchFromServer("https://api.example.com/data1") }
val data2 = async { fetchFromServer("https://api.example.com/data2") }
println("Data1: ${data1.await()}")
println("Data2: ${data2.await()}")
}

suspend fun fetchFromServer(url: String): String {
delay(1000) // Simulated network delay
return "Response from $url"
}

In this example, we use async and await to fetch data concurrently. This reduces the total time and increases efficiency.

Why Should We Use Coroutines?

For new Android developers, understanding why we should use coroutines can be crucial for writing efficient and clean code. Here are some key reasons:

Simpler Code: Coroutines make asynchronous code look and behave more like synchronous code, making it easier to read and understand.

Lightweight: Coroutines are much lighter than threads. You can run thousands of coroutines with minimal memory overhead.

Structured Concurrency: Coroutines provide structured concurrency, which means they help manage the lifecycle of coroutines within the scope of specific operations, reducing the chance of memory leaks.

Built-in Support: Kotlin coroutines are built into the language and have excellent support within Android development, including libraries like kotlinx.coroutines that provide additional features and utilities.

Example: Simplifying Asynchronous Code with Coroutines

Without coroutines, handling asynchronous operations can be complex and involve callbacks:

fun fetchData(callback: (String) -> Unit) {
Thread {
val data = fetchFromServer("https://api.example.com/data")
callback(data)
}.start()
}

Using coroutines simplifies this:

import kotlinx.coroutines.*

fun main() = runBlocking {
val data = fetchFromServer("https://api.example.com/data")
println(data)
}

suspend fun fetchFromServer(url: String): String {
delay(1000)
return "Response from $url"
}

🎨 Using the Code

Here is an example of how these classes can be used:

fun main() = runBlocking {
val featureToggle = FeatureToggle()
val userInterface = UserInterface(featureToggle)

userInterface.render()
}

class UserInterface(private val featureToggle: FeatureToggle) {
fun render() {
if (featureToggle.isNewUIEnabled()) {
// New UI code
println("Rendering new UI")
} else {
// Old UI code
println("Rendering old UI")
}
}
}

class FeatureToggle {
private val features = mapOf(
"newUI" to true // Feature toggle states are managed here
)

fun isNewUIEnabled(): Boolean {
return features["newUI"] ?: false
}
}

This structure makes it easy to manage feature toggles and gradually roll out new features.

Clean Code Standards for Concurrency

When implementing concurrency, it’s essential to follow some clean code principles:

1. Use Meaningful Names

Using meaningful and descriptive names for threads and concurrent operations increases code readability.

Bad Example:

fun task1() { /* ... */ }
fun task2() { /* ... */ }

Good Example:

fun fetchUserData() { /* ... */ }
fun fetchProductData() { /* ... */ }

2. Use the Coroutine Library for Managing Threads

Kotlin’s coroutine library makes managing threads and concurrent operations simpler and safer.

Bad Example:

Thread {
// Thread code
}.start()

Good Example:

GlobalScope.launch {
// Coroutine code
}

3. Add Error Handling

Handling errors properly during concurrent operations increases code reliability.

Example:

import kotlinx.coroutines.*

fun main() = runBlocking {
try {
val data1 = async { fetchFromServer("https://api.example.com/data1") }
val data2 = async { fetchFromServer("https://api.example.com/data2") }
println("Data1: ${data1.await()}")
println("Data2: ${data2.await()}")
} catch (e: Exception) {
println("Error fetching data: ${e.message}")
}
}

suspend fun fetchFromServer(url: String): String {
delay(1000) // Simulated network delay
if (url.contains("error")) throw Exception("Simulated error")
return "Response from $url"
}

Conclusion

Concurrency in Kotlin helps manage large and complex operations more efficiently. By using meaningful names, utilizing the coroutine library, and adding error handling, you can implement concurrency following clean code standards. These principles will make your concurrent operations more effective and increase your code quality.

Use this article as a guide to understanding concurrency in Kotlin and applying clean code standards. If you would like more articles on other topics or principles, feel free to request them with specific topics or examples.

According to the Clean Code Cookbook, clean code is well-structured, concise, and uses meaningful names for variables, functions, and classes. It follows best practices and design patterns, favoring readability and behavior over performance and implementation details .

Github project for a similar usage in this article: https://github.com/birdeveloper/kotlin_concurrency_project

By writing clean code and covering it with tests, you can then focus on improving performance by addressing the most critical bottlenecks. This approach ensures that you do not sacrifice code readability and maintainability for premature optimization .

--

--