Progression Partie 7 sur 12

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 :

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 :

app/build.gradle.kts
import java.util.Properties
...

Après le bloc plugins et avant le bloc android, ajoute :

app/build.gradle.kts
...
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 :

app/build.gradle.kts
buildConfigField(
    type = "String",
    name = "REST_COUNTRIES_API_KEY",
    value = "\"$restCountriesApiKey\""
)

Le bloc ressemble maintenant à ceci :

app/build.gradle.kts
...
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 :

app/build.gradle.kts
...
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

data/CountryApi.kt
...
import io.ktor.client.HttpClient
...
class CountryApi(
    private val client: HttpClient,
) {
}

À l’intérieur de la classe CountryApi, ajoute la fonction getCountries

data/CountryApi.kt
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 :

data/CountryApi.kt
...
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 :

data/CountryApi.kt
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 :

data/CountryApi.kt
.get("https://api.restcountries.com/countries/v5") {
}

À l’intérieur de ce bloc, ajoute :

data/CountryApi.kt
bearerAuth(BuildConfig.REST_COUNTRIES_API_KEY)

La requête devient :

data/CountryApi.kt
...
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 :

BASH
Authorization: Bearer VOTRE_CLE_API

Demander uniquement les champs nécessaires

Toujours à l’intérieur du bloc get, sous bearerAuth, ajoute :

data/CountryApi.kt
parameter(
    key = "response_fields",
    value = "names.common,codes.fifa,flag.emoji"
)

Le bloc de la requête doit maintenant contenir :

data/CountryApi.kt
...
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 à :

CURL
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 :

data/CountryApi.kt

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.