Kotlin launch is a co-routine operating under the model 'fire-and-forget'. It returns a Job object that we can use to see if it is active or cancel it. It is also non-blocking.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Launch a background coroutine
val job = launch {
delay(1000L)
println("World!")
}
println("Hello,") // This prints immediately while 'World!' is waiting
job.join() // (Optional) Wait for the launch block to finish
}
If you want to fetch user data from a network API and use that data, launch won't cut it because it can't return the data. You must use async, which is where await() comes into play:
This is a good example of launch use-case
class ProfileViewModel : ViewModel() {
// viewModelScope automatically cancels the launch if the screen closes
fun updateProfilePicture(imagePath: String) {
viewModelScope.launch(Dispatchers.IO) {
// 1. Move to the I/O thread pool
uploadImage(imagePath)
// 2. Fire-and-forget background work
logAnalytics("Image Uploaded")
}
}
}
```</T>
We shift the entire code to another job.
async and await
Whenever we would like to use asynchronous task and possible return results, we can use async/await and a very simple example would look like this:-
Approach 1: General approach
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.delay
// 1. Define the asynchronous function using 'suspend'
suspend fun fetchUserData(userId: String): String {
// optional: force this work onto the I/O thread pool
return withContext(Dispatchers.IO) {
delay(2000L) // Simulating a 2-second network request
"User Profile Data for $userId"
}
}
And to call it, we use the following code:-
fun main() = runBlocking {
println("Fetching user...")
// Looks like synchronous code, but it is completely asynchronous!
val result = fetchUserData("123")
println("Result received: $result")
}
Approach 2: Explicit Parallelism (Using async { })
Using async keyword to call async function
import kotlinx.coroutines.*
suspend fun getStockPrice(): Int {
delay(1000) // Simulate network
return 50
}
suspend fun getCryptoPrice(): Int {
delay(1000) // Simulate network
return 60000
}
fun main() = runBlocking {
println("Starting parallel fetches...")
// Both start executing at the exact same time
val stockDeferred: Deferred<Int> = async { getStockPrice() }
val cryptoDeferred: Deferred<Int> = async { getCryptoPrice() }
// Wait for both to finish and get results using .await()
val totalPortfolio = stockDeferred.await() + cryptoDeferred.await()
println("Total value: \$${totalPortfolio}")
// Total time elapsed is ~1 second, not 2 seconds!
}
3. Creating Extension Functions that Return Deferred and forcing developer to call .await()
In languages like JavaScript, when you call an async function, a Promise is spun up into the global space. If the user navigates away or closes a screen, that Promise keeps running in the background, causing memory leaks or waste.
Kotlin prevents this through CoroutineScope. Every coroutine must belong to a scope. If the scope dies (e.g., the user closes an Android screen or a Ktor HTTP request finishes), all coroutines inside that scope are automatically canceled.
import kotlinx.coroutines.*
// This function can only be called if a CoroutineScope is available
fun CoroutineScope.fetchDataAsync(): Deferred<String> = async {
delay(1000)
"Async Result"
}
You are saying: "This function is structurally bound to whoever called it." It forces the function to inherit the caller's lifecycle, ensuring it can never become a rogue background task.
Comments