Envoyer notre première requête HTTP
Notre projet possède maintenant :
- un HttpClient configuré ;
- les DTO correspondant à la réponse JSON ;
- une clé d’API enregistrée dans local.properties.
Nous allons créer une classe appelée CountryApi. Elle sera responsable de la communication avec REST Countries.
Son rôle sera de construire la requête HTTP, d’ajouter la clé d’API, d’envoyer la requête et de convertir la réponse JSON en CountriesResponseDto.
Rendre la clé accessible dans le code Kotlin
Notre clé est actuellement enregistrée dans local.properties :
REST_COUNTRIES_API_KEY=rc_live_xxxxxxxxxxxxxxxxxxxxx
Le code Kotlin de l’application ne peut pas lire directement cette propriété. Nous allons donc demander à Gradle de l’ajouter dans la classe BuildConfig.
Ouvre le fichier app/build.gradle.kts
Au début du fichier, avant le bloc plugins, ajoute cet import :
import java.util.Properties
...
Après le bloc plugins et avant le bloc android, ajoute :
...
val localProperties = Properties().apply {
rootProject
.file("local.properties")
.inputStream()
.use(::load)
}
val restCountriesApiKey = localProperties.getProperty("REST_COUNTRIES_API_KEY").orEmpty()
android {
...
}
...
Dans le bloc android, cherche defaultConfig.
À l’intérieur de defaultConfig, après versionName, ajoute :
buildConfigField(
type = "String",
name = "REST_COUNTRIES_API_KEY",
value = "\"$restCountriesApiKey\""
)
Le bloc ressemble maintenant à ceci :
...
android {
defaultConfig {
applicationId = "com.composechef.countryexplorer"
minSdk = 24
targetSdk = 37
versionCode = 1
versionName = "1.0"
buildConfigField(
type = "String",
name = "REST_COUNTRIES_API_KEY",
value = "\"$restCountriesApiKey\""
)
}
}
Dans le même bloc android, cherche buildFeatures.
Ajoute buildConfig = true à l’intérieur :
...
buildFeatures {
compose = true
buildConfig = true
}
...
La clé sera maintenant accessible depuis le code Kotlin avec :
BuildConfig.REST_COUNTRIES_API_KEY
Clique sur Sync Now pour synchroniser le projet avec Gradle.
Déclarer la classe CountryApi
Dans le package data crée le fichier CountryApi.kt et Déclare la classe CountryApi
...
import io.ktor.client.HttpClient
...
class CountryApi(
private val client: HttpClient,
) {
}
À l’intérieur de la classe CountryApi, ajoute la fonction getCountries
suspend fun getCountries(): CountriesResponseDto {
}
La fonction est déclarée avec le mot-clé suspend parce qu’une requête réseau ne retourne pas son résultat instantanément.
Elle doit attendre que la requête soit envoyée, que le serveur la traite et que la réponse revienne sur l’appareil, sans bloquer le thread principal.
Envoyer la requête GET
À l’intérieur de getCountries(), ajoute :
...
import io.ktor.client.request.get
import io.ktor.client.call.body
...
return client.get("https://api.restcountries.com/countries/v5")
.body()
La fonction doit maintenant ressembler à ceci :
suspend fun getCountries(): CountriesResponseDto {
return client.get("https://api.restcountries.com/countries/v5")
.body()
}
La fonction get correspond à la méthode HTTP GET.
Le type de retour CountriesResponseDto indique à Ktor que le contenu reçu doit être converti dans cette classe lorsque nous appelons body().
Ajouter la clé d’API
Nous devons maintenant configurer la requête.
Dans l’appel get, ajoute un bloc d’instructions :
.get("https://api.restcountries.com/countries/v5") {
}
À l’intérieur de ce bloc, ajoute :
bearerAuth(BuildConfig.REST_COUNTRIES_API_KEY)
La requête devient :
...
import com.composechef.countryexplorer.BuildConfig
import io.ktor.client.request.bearerAuth
...
return client.get("https://api.restcountries.com/countries/v5") {
bearerAuth(BuildConfig.REST_COUNTRIES_API_KEY)
}
.body()
bearerAuth ajoute automatiquement l’en-tête HTTP suivant :
Authorization: Bearer VOTRE_CLE_API
Demander uniquement les champs nécessaires
Toujours à l’intérieur du bloc get, sous bearerAuth, ajoute :
parameter(
key = "response_fields",
value = "names.common,codes.fifa,flag.emoji"
)
Le bloc de la requête doit maintenant contenir :
...
import io.ktor.client.request.parameter
...
.get("https://api.restcountries.com/countries/v5") {
bearerAuth(BuildConfig.REST_COUNTRIES_API_KEY)
parameter(
key = "response_fields",
value = "names.common,codes.fifa,flag.emoji"
)
}
Ktor ajoutera automatiquement ce paramètre à l’URL et se chargera de l’encoder correctement.
La requête envoyée correspondra à :
https://api.restcountries.com/countries/v5?response_fields=names.common,codes.fifa,flag.emoji
Faut-il utiliser Dispatchers.IO ?
Il n’est pas nécessaire d’entourer cette requête avec withContext(Dispatchers.IO).
Les appels réseau de Ktor utilisent déjà des API suspendues et ne bloquent pas le thread principal pendant l’attente de la réponse.
la version finale de CountryApi
À ce stade, CountryApi.kt doit contenir :
package com.composechef.countryexplorer.data
import com.composechef.countryexplorer.BuildConfig
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.get
import io.ktor.client.request.parameter
class CountryApi(
private val client: HttpClient,
) {
suspend fun getCountries(): CountriesResponseDto {
return client
.get("https://api.restcountries.com/countries/v5") {
bearerAuth(BuildConfig.REST_COUNTRIES_API_KEY)
parameter(
key = "response_fields",
value = "names.common,codes.fifa,flag.emoji"
)
}
.body()
}
}
Notre classe CountryApi peut maintenant récupérer et convertir les données retournées par REST Countries.
Cependant, elle retourne encore des DTO qui suivent la structure du serveur. Dans la prochaine partie, nous allons créer un repository chargé de transformer ces DTO en une liste de Country directement utilisable par le reste de l’application.