Skip to Content
Course content

189: Building a Command-Line Weather App with Coroutines

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're a waiter at a busy diner. A table of three people orders breakfast. If you were a "blocking" waiter, you'd take the first person's order for pancakes, walk to the kitchen, and stand there staring at the chef until the pancakes were plated. Only then would you return to the table to take the second person's order for eggs. It would take forever, and your customers would be furious.

Instead, you do what any sane waiter does: you take all three orders, hand the tickets to the kitchen, and then go check on other tables or pour some coffee while the food is cooking. You've initiated three separate tasks and you'll just circle back to collect the plates as they're ready. This is exactly how we're going to handle weather data for multiple cities using Kotlin Coroutines.

The bottleneck of sequential requests

When you're building a CLI tool that hits a network API, the slowest part is always the "wait." If you want the weather for New York, London, and Tokyo, and each request takes 500ms, doing them sequentially takes 1.5 seconds. That doesn't sound like much, but in a real app with more data, that lag is palpable. I've seen developers struggle with this for years, trying to manage complex callbacks or manual threads, but async makes this trivial.

Firing off requests with async

In our weather app, we don't just want to start a task (which is what launch does); we want a result back. That's why we use async. It returns a Deferred object, which is basically a promise that "I'll have your weather data eventually."

import kotlinx.coroutines.*

// Simulating a network call to a weather API
suspend fun fetchWeather(city: String): String {
    delay(500) // Simulate network latency
    val temperatures = mapOf("New York" to "15°C", "London" to "10°C", "Tokyo" to "18°C")
    return "${city}: ${temperatures[city] ?: "Unknown"}"
}

fun main() = runBlocking {
    val cities = listOf("New York", "London", "Tokyo")
    
    println("Fetching weather updates...")
    
    // We map each city to an async block. 
    // This starts all requests almost simultaneously.
    val deferredResults = cities.map { city ->
        async { 
            fetchWeather(city) 
        }
    }

    // Now we wait for all of them to finish
    val results = deferredResults.awaitAll()
    
    results.forEach { println(it) }
    println("All updates received!")
}

Orchestrating the results with awaitAll

Notice that I didn't call .await() inside the map loop. If I had, I would have been right back to being that "blocking waiter"—waiting for New York to finish before even asking for London's weather. By collecting a list of Deferred objects first and then calling awaitAll(), we ensure that the requests are running in parallel.

I should mention: runBlocking is used here because this is a main function in a CLI app. In a production Android or Ktor app, you'd rarely use it, as you don't want to block the main thread of the entire application. But for a script or a tool, it's the perfect bridge between the non-coroutine world of main() and the suspending world of our weather logic.

Handling the inevitable network failure

In the real world, APIs fail. One city might time out while the others succeed. If one of your async blocks throws an exception, it can cancel the entire parent scope. To prevent one bad API response from crashing your whole weather dashboard, you can wrap the logic inside the async block with a try-catch. It's a small detail, but it's the difference between a professional tool and a fragile one.




📋 Practical Task

Build a Multi-City Temperature Comparison Tool

Modify the weather app logic to do more than just list the temperatures. Your task is to create a program that:

  • Fetches weather for a list of at least five cities concurrently using async.
  • Simulates a failure for one specific city (e.g., if the city is "Atlantis", throw an IOException).
  • Uses a try-catch block inside the async coroutine so that the program still prints the weather for the successful cities even if one fails.
  • After gathering all results, identifies and prints which city has the highest temperature.

Hint: You'll need to change the return type of your fetch function from a String to an Int or a custom data class to make the temperature comparison possible.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.