Open Weather HTTP client with Ktor

Most of programmers think Kotlin is just a language for Android. Although Kotlin is a fantastic language for Android development, it can use in almost every programming areas that you imagine. You can create RESTful APIs, websites, telegram bots, desktop applications and so on. So if you know Kotlin, you don't need to learn another language. Just learn a Kotlin framework.

In this post, I want to create a HTTP client for weather. I use Ktor framework and Open Weather API for this application. Ktor is a framework to easily build connected applications – web applications, HTTP services, mobile and browser applications. Modern connected applications need to be asynchronous to provide the best experience to users, and Kotlin coroutines provide awesome facilities to do it in an easy and straightforward way.

Open Weather API

Open Weather website has a great API for weather data. Just go to openweathermap.org and sign up. Now you can access this API with your token. Really easy. For our application we want to get current weather for a custom city and show data in console. Our result will be like this:

Enter City Name: new york
*****OPEN WEATHER MAP*****
City         : New York
Country      : US
Current temp : 10.79 C
Max temp     : 12.0 C
Min temp     : 9.44 C
Pressure     : 1012.0

For more information see: Current weather data documentation

Add dependencies

Open IntelliJ IDEA and create a new gradle project. I use java 1.8 for this project. Wait until IntelliJ IDEA generates a project and installs the dependencies.

A Ktor client application needs at least two dependencies:

  • ktor-client-core: is a core dependency that provides the main client functionality.
  • ktor-client-<CLIENT_ENGINE>: is a dependency for an engine processing network requests. In this project, I use Apache as an engine. For more information about client engines see: HTTP client engines

Open weather API returns JSON, so we need install JsonFeature for this project. I use Gson therefore I add ktor-client-gson to my project.

Add These codes to gradle.build file:

dependencies {
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-apache:$ktor_version"
implementation "io.ktor:ktor-client-gson:$ktor_version"
}

We didn't specify ktor_version variable yet, so add this line to gradle.properties file:

ktor_version=1.5.3

Change 1.5.3 to latest Ktor version. Build gradle again. IntelliJ will download all dependencies and add them to our project.

Now we have our Kotlin project and we installed all dependencies. Let's write some code.

Create a client

First of all we need a client. Let's build it. Create a file called Application.kt in kotlin directory and add these lines to it:

import io.ktor.client.*
import io.ktor.client.engine.apache.*
import io.ktor.client.request.*
import io.ktor.http.*

suspend fun main() {
val client = HttpClient(Apache)
val response: String = client.request {
url("https://google.com")
method = HttpMethod.Get
}
client.close()

println(response)
}

We create a HttpClient with Apache client engine. Then we send a GET request to https://google.com and save its response in a string variable. When our work finish with client we need to close it and free the memory.

Run Application.kt. response is printed in console as a string. Until now, we create a HTTP client with Apache engine, send a GET request to google.com and receive a response as a string but we want to send requests to Open Weather API, not Google website.

GET request to API

Let's change the URL to Open Weather API.

...
val response: String = client.request {
url("https://api.openweathermap.org/data/2.5/weather")
method = HttpMethod.Get
...

So, we send a request to API but if we run this application, we have an error. There are no parameters for our token, city name or other options we see in API documentation. Let's fix that.

Add parameters to Get request

These two parameters are necessary for getting current weather data by city name:

  • appid: Your unique API key
  • q: City name

By default, we receive weather data in Kelvin but I like Celsius, so I add another parameter:

  • units: For temperature in Celsius use units=metric
...
method = HttpMethod.Get
parameter("appid", "YOUR_TOKEN")
parameter("q", "YOUR_CITY")
parameter("units", "metric")
}
...
  • Replace YOUR_TOKEN with your token from Open Weather. You can use sys.env() to get token from environment variables for more security.
  • Replace YOUR_CITY with a custom city name you want to receive its data.

GSON

Our application receive responses properly but we can't use them. They are in JSON format but we receive them as a string. So we need to add JsonFeature to our application.

JsonFeature can be used to serialize/deserialize JSON data when sending requests and receiving responses. This functionality is provided for JVM by using the Gson or Jackson libraries. Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. There is a simple package for using Gson in Ktor client called ktor-client-gson. We add it before to gradle.build.

Add JsonFeature to application

Just add these lines to your code:

...
import io.ktor.client.engine.apache.*
import io.ktor.client.features.json.*
import io.ktor.client.request.*
...

...
val client = HttpClient(Apache) {
install(JsonFeature)
}
val response: String = client.request {
...

Create CurrentWeather data class

We saw earlier Gson converts a JSON string to an equivalent Java or Kotlin object. So we need a data object for converting JSON response to that object. JSON response is like this:

{
  "coord": {
        "lon": -74.006,
        "lat": 40.7143
    },
    "weather": [
        {
            "id": 804,
            "main": "Clouds",
            "description": "overcast clouds",
            "icon": "04n"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 10.88,
        "feels_like": 9.33,
        "temp_min": 10,
        "temp_max": 12,
        "pressure": 1013,
        "humidity": 50
    },
    "visibility": 10000,
    "wind": {
        "speed": 2.57,
        "deg": 210
    },
    "clouds": {
        "all": 90
    },
    "dt": 1618825416,
    "sys": {
        "type": 1,
        "id": 5141,
        "country": "US",
        "sunrise": 1618827040,
        "sunset": 1618875545
    },
    "timezone": -14400,
    "id": 5128581,
    "name": "New York",
    "cod": 200
}

We want to create a class for this JSON response. Create a new data class file named CurrentWeather.kt and add these lines to it:

data class CurrentWeather(
val coord: Coordinate,
val weather: List<Weather>,
val base: String,
val main: Main,
val visibility: String,
val wind: Wind,
val clouds: Clouds,
val dt: Int,
val sys: Sys,
val timezone: Int,
val id: Int,
val name: String,
val code: Int
)

data class Coordinate(
val lon: Double,
val lat: Double
)

data class Weather(
val id: Int,
val main: String,
val description: String,
val icon: String
)

data class Main(
val temp: Double,
val feelsLike: Double,
val temp_min: Double,
val temp_max: Double,
val pressure: Double,
val humidity: Double
)

data class Wind(
val speed: Double,
val deg: Double
)

data class Clouds(
val all: Double
)

data class Sys(
val type: Int,
val id: Int,
val country: String,
val sunrise: String,
val sunset: String
)

Convert JSON to Kotlin Object

We have our JSON response. We have our Kotlin object. it's time to convert Json String to Kotlin object. Just change response type from String to CurrentWeather:

...
}
val response: CurrentWeather = client.request {
url("https://api.openweathermap.org/data/2.5/weather")
...

Run Application.kt and see a fantastic output in console. Congratulations, we convert JSON to Kotlin object.

Print result as a formatted string

We have our object now and it's not good to print it directly in console.

...
client.close()

println("*****OPEN WEATHER MAP*****")
println("City : ${response.name}")
println("Country : ${response.sys.country}")
println("Current temp : ${response.main.temp} C")
println("Max temp : ${response.main.temp_max} C")
println("Min temp : ${response.main.temp_min} C")
println("Pressure : ${response.main.pressure}")
}

Run the program and see a beautiful output for our weather data:

*****OPEN WEATHER MAP*****
City         : New York
Country      : US
Current temp : 10.56 C
Max temp     : 12.0 C
Min temp     : 8.33 C
Pressure     : 1012.0

Complete application

So far, we create a HTTP client, send a request to Open Weather API and receive a JSON String for data. Then we convert JSON String to Kotlin object using JsonFeature and Gson and print our data to the console with a beautiful formatted string. Our application is almost complete but we need to make some changes to have a better code with more functionality.

Refactor client as a function

In our code, logic is completely in main function and I think it's a good idea to refactor some codes to separate functions:

...
suspend fun main() {
val response = currentWeather()

println("*****OPEN WEATHER MAP*****")
...
}

suspend fun currentWeather(): CurrentWeather {
val client = HttpClient(Apache) {
install(JsonFeature)
}
val response: CurrentWeather = client.request {
url("https://api.openweathermap.org/data/2.5/weather")
method = HttpMethod.Get
parameter("appid", "YOUR_TOKEN")
parameter("q", "YOUR_CITY")
parameter("units", "metric")
}
client.close()

return response
}

With a small change, our code is more readable and concise now.

Get city name from user

Suppose I want to have a travel to London, Berlin and Paris but first of all, I need weather conditions in this cities. I don't like getting wet from the rain. Now in my code I need change city name every time to get weather data for various cities. Let's fix this problem:

...
suspend fun main() {
print("Enter City Name: ")
val cityName = readLine() ?: "DEFAULT_CITY"
val response = currentWeather(city = "YOUR_CITY", token = "YOUR_TOKEN")
...
}

suspend fun currentWeather(city: String, token: String, units: String = "metric"): CurrentWeather {
val response: CurrentWeather = client.request {
url("https://api.openweathermap.org/data/2.5/weather")
method = HttpMethod.Get
parameter("appid", token)
parameter("q", city)
parameter("units", units)
}
...

Now run the program, it asks you to enter a city name and gives you weather data for any cities you want.

Enter City Name: berlin
*****OPEN WEATHER MAP*****
City         : Berlin
Country      : DE
Current temp : 11.6 C
Max temp     : 12.78 C
Min temp     : 10.0 C
Pressure     : 1020.0

Wow, that's really cold. Maybe it's not a good time for me to travel to Berlin!

Improve formatted string

I can't forgive myself. I have used seven println  in my code. That's really ugly. Let's fix that:

...
val response = currentWeather(city = "YOUR_CITY", token = "YOUR_TOKEN")

val result = """
*****OPEN WEATHER MAP*****
City : ${response.name}
Country : ${response.sys.country}
Current temp : ${response.main.temp} C
Max temp : ${response.main.temp_max} C
Min temp : ${response.main.temp_min} C
Pressure : ${response.main.pressure}
""".trimIndent()

println(result)
}

...

This is better now. Don't you think so?

Conclusion

Most of programmers think Kotlin is just a language for Android. Although Kotlin is a fantastic language for Android development, it can use in almost every programming areas that you imagine. You can create RESTful APIs, websites, telegram bots, desktop applications and so on.

In this post, I used a Kotlin framework called Ktor for creating a simple HTTP client app. it receives weather data from Open Weather using its API and gives us a really nice formatted string in console. Now it's time to think. Is Kotlin just for Android development? Or is it an awesome programming language in all sort of applications?

You can download sourcecode from Github: OpenWeatherKtor repository

Mohammad Ebrahimi