Back to Blog

HTTP & APIs in Compose Desktop: Ktor Client & JSON Serialization | Kotlin Desktop #8

Sandy LaneSandy Lane

Video: HTTP & APIs in Compose Desktop: Ktor Client & JSON Serialization | Kotlin Desktop #8 by Taught by Celeste AI - AI Coding Coach

Watch full page →

HTTP & APIs in Compose Desktop: Ktor Client & JSON Serialization

In this tutorial, you'll learn how to build a simple Weather App using Compose Desktop with Kotlin. The app fetches weather data asynchronously from the Open-Meteo API using Ktor Client with the CIO engine and parses JSON responses with kotlinx.serialization, demonstrating practical use of suspend functions, coroutine scopes, and UI state handling.

Code

import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

// Data classes mapping the JSON response from Open-Meteo API
@Serializable
data class WeatherResponse(
  val latitude: Double,
  val longitude: Double,
  val current_weather: CurrentWeather
)

@Serializable
data class CurrentWeather(
  @SerialName("temperature") val temperatureCelsius: Double,
  @SerialName("windspeed") val windSpeedKmh: Double,
  @SerialName("weathercode") val weatherCode: Int
)

// Setup Ktor HttpClient with JSON serialization
val client = HttpClient(CIO) {
  install(ContentNegotiation) {
    json(Json { ignoreUnknownKeys = true })
  }
}

// Suspend function to fetch weather data asynchronously
suspend fun fetchWeather(latitude: Double, longitude: Double): WeatherResponse {
  val url = "https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude¤t_weather=true"
  return client.get(url).body()
}

fun main() = Window(title = "Weather App") {
  var weather by remember { mutableStateOf(null) }
  var isLoading by remember { mutableStateOf(false) }
  var errorMessage by remember { mutableStateOf(null) }
  val scope = rememberCoroutineScope()

  // Fetch weather on composition
  LaunchedEffect(Unit) {
    isLoading = true
    errorMessage = null
    try {
      weather = fetchWeather(52.52, 13.405) // Berlin coordinates
    } catch (e: Exception) {
      errorMessage = "Failed to load weather: ${e.localizedMessage}"
    } finally {
      isLoading = false
    }
  }

  Surface(modifier = Modifier.fillMaxSize().padding(16.dp)) {
    when {
      isLoading -> {
        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
          CircularProgressIndicator()
        }
      }
      errorMessage != null -> {
        Text(errorMessage ?: "Unknown error", color = MaterialTheme.colors.error)
      }
      weather != null -> {
        Column {
          Text("Current Weather for Berlin", style = MaterialTheme.typography.h6)
          Spacer(Modifier.height(8.dp))
          Text("Temperature: ${weather!!.current_weather.temperatureCelsius} °C")
          Text("Wind Speed: ${weather!!.current_weather.windSpeedKmh} km/h")
          Text("Weather Code: ${weather!!.current_weather.weatherCode}")
          Spacer(Modifier.height(16.dp))
          Button(onClick = {
            scope.launch {
              isLoading = true
              errorMessage = null
              try {
                weather = fetchWeather(52.52, 13.405)
              } catch (e: Exception) {
                errorMessage = "Failed to refresh weather: ${e.localizedMessage}"
              } finally {
                isLoading = false
              }
            }
          }) {
            Text("Refresh")
          }
        }
      }
    }
  }
}

Key Points

  • Ktor Client with the CIO engine enables efficient asynchronous HTTP requests in Kotlin Desktop apps.
  • kotlinx.serialization with ContentNegotiation plugin simplifies JSON parsing into @Serializable data classes.
  • Suspend functions and coroutine scopes integrate network calls smoothly with Compose UI's reactive state system.
  • Loading and error states are managed with mutable state and conditional UI rendering using when blocks.
  • LaunchedEffect triggers initial data fetching when the composable enters composition, ensuring up-to-date data on launch.