Récupérer des données sur Internet

1. Avant de commencer

La plupart des applications Android disponibles sur le marché se connectent à Internet pour effectuer certaines opérations réseau, comme la récupération d'e-mails, de messages ou d'informations similaires depuis un serveur backend. Gmail, YouTube et Google Photos sont des exemples d'applications qui se connectent à Internet pour afficher les données utilisateur.

Dans cet atelier de programmation, vous utiliserez des bibliothèques Open Source pour créer des couches réseau et obtenir des données à partir d'un serveur backend. Cela simplifie grandement la récupération des données et permet à l'application de respecter les bonnes pratiques d'Android, comme effectuer les opérations sur un thread d'arrière-plan. Vous allez également mettre à jour l'interface utilisateur de l'application si Internet est lent ou indisponible afin d'informer l'utilisateur de tout problème de connectivité réseau.

Ce que vous devez déjà savoir

  • Créer et utiliser des fragments.
  • Utiliser les composants d'architecture Android ViewModel et LiveData.
  • Ajouter des dépendances dans un fichier Gradle.

Points abordés

  • Qu'est-ce qu'un service Web REST ?
  • Se connecter à un service Web REST sur Internet à l'aide de la bibliothèque Retrofit et obtenir une réponse.
  • Utiliser la bibliothèque Moshi pour analyser la réponse JSON dans un objet de données.

Objectifs de l'atelier

  • Modifier une application de démarrage pour effectuer une requête API de service Web et gérer la réponse.
  • Implémenter une couche réseau pour votre application à l'aide de la bibliothèque Retrofit.
  • Analysez la réponse JSON du service Web dans les objets LiveData de votre application avec la bibliothèque Moshi.
  • Utilisez la prise en charge des coroutines de Retrofit pour simplifier le code.

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio.
  • Du code de démarrage de l'application MarsPhotos.

2. Présentation de l'application

Dans ce parcours, vous allez utiliser une application de démarrage appelée MarsPhotos, qui montre des images de la surface de Mars. Cette application se connecte à un service Web pour récupérer et afficher les photos de Mars. Les images sont de vraies photos de Mars prises par les rovers de la NASA. Vous trouverez ci-dessous la capture d'écran de l'application finale, qui contient une grille de vignettes d'images, construite avec un RecyclerView.

ea967f35fa98d72b.png

La version de l'application que vous allez créer ne présente pas beaucoup de flashs visuels. Elle se concentre sur la partie réseau de l'application pour se connecter à Internet et télécharger les données brutes de la propriété via un service Web. Pour vous assurer que les données sont correctement récupérées et analysées, il vous suffit d'imprimer le nombre de photos reçues du serveur backend dans un affichage de texte :

1a7e99791caf8d96.png

3. Découvrez l'application de démarrage MarsPhotos

Télécharger le code de démarrage

Cet atelier de programmation fournit un code de démarrage que vous pouvez étendre avec les fonctionnalités qui y sont enseignées. Le code de démarrage peut contenir du code que vous avez déjà vu, ou non, dans les ateliers de programmation précédents. Vous en apprendrez davantage sur le code que vous ne connaissez pas dans les prochains ateliers de programmation.

Si vous utilisez le code de démarrage de GitHub, notez que le nom du dossier est android-basics-kotlin-mars-photos-app. Sélectionnez ce dossier lorsque vous ouvrirez le projet dans Android Studio.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Exécuter 8de56cba7583251f.png pour créer et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.

Exécuter le code de démarrage

  1. Ouvrez le projet téléchargé dans Android Studio. Le nom du dossier du projet est android-basics-kotlin-mars-photos-app. La structure du dossier du code de démarrage doit se présenter comme suit.
  2. Dans le volet Android, développez application > java. Notez que l'application comporte un dossier de package nommé overview. Il s'agit de la couche de l'UI de l'application.

d428a16d480349d1.png

  1. Exécutez l'application. Lorsque vous compilez et exécutez l'application, l'écran suivant devrait s'afficher avec un espace réservé en son centre. À la fin de cet atelier de programmation, vous mettrez à jour ce texte d'espace réservé avec le nombre de photos récupérées.

406c7bcc0f07267c.png

  1. Parcourez les fichiers pour comprendre le code de démarrage. Pour les fichiers de mise en page, vous pouvez utiliser l'option Split (Diviser) disponible dans le coin supérieur droit pour afficher un aperçu de la mise en page et du fichier XML en même temps.

Tutoriel du code de démarrage

Dans cette tâche, vous allez vous familiariser avec la structure du projet. Voici une présentation des fichiers et dossiers importants du projet.

OverviewFragment:

  • Il s'agit du fragment affiché dans MainActivity. Le texte d'espace réservé que vous avez vu à l'étape précédente s'affiche dans ce fragment.
  • Dans l'atelier de programmation suivant, ce fragment affichera les données reçues des photos de Mars du serveur backend.
  • Cette classe contient une référence à l'objet OverviewViewModel.
  • OverviewFragment possède une fonction onCreateView() qui augmente la mise en page de fragment_overview à l'aide de la liaison de données. Cette fonction définit le propriétaire du cycle de vie de la liaison sur lui-même et définit la variable viewModel dans l'objet de liaison.
  • Étant donné que le propriétaire du cycle de vie est attribué, toute LiveData utilisée dans la liaison de données sera automatiquement observée pour toute modification, et l'UI sera mise à jour en conséquence.

OverviewViewModel:

  • Il s'agit du modèle de visualisation correspondant à OverviewFragment.
  • Cette classe contient une propriété MutableLiveData nommée _status et sa propriété de support. Si vous modifiez la valeur de cette propriété, le texte d'espace réservé qui s'affiche à l'écran est mis à jour.
  • La méthode getMarsPhotos() met à jour la réponse de l'espace réservé. Plus tard dans l'atelier de programmation, vous utiliserez ce code pour afficher les données extraites du serveur. L'objectif de cet atelier de programmation est de mettre à jour la LiveData de status dans le ViewModel à l'aide de données réelles issues d'Internet.

res/layout/fragment_overview.xml :

  • Cette mise en page est configurée pour utiliser la liaison de données et comprend un seul TextView.
  • Il déclare une variable OverviewViewModel, puis associe le status du ViewModel au TextView.

MainActivity.kt : la seule tâche de cette activité est de charger la mise en page de l'activité, activity_main.

layout/activity_main.xml: correspond à la mise en page principale de l'activité avec un seul élément FragmentContainerView pointant vers fragment_overview. Le fragment de vue d'ensemble sera instancié au lancement de l'application.

4. Présentation de l'application

Dans cet atelier de programmation, vous créerez une couche pour le service réseau qui communique avec le serveur backend et récupérera les données requises. Pour ce faire, vous utiliserez une bibliothèque tierce appelée "Retrofit". Nous y reviendrons plus tard. Le ViewModel communique directement avec cette couche réseau, le reste de l'application est transparent pour cette implémentation.

1c2493b9e9e1eef.png

OverviewViewModel est responsable de l'appel réseau pour obtenir les données des photos de Mars. Dans ViewModel, vous utilisez LiveData avec une liaison de données compatible avec le cycle de vie pour mettre à jour l'interface utilisateur de l'application lorsque les données changent.

5. Services Web et Retrofit

Les données des photos de Mars sont stockées sur un serveur Web. Pour intégrer ces données dans votre application, vous devez établir une connexion et communiquer avec le serveur sur Internet.

37f7c367e182b4f9.png

d99aca47f5947a78.png

Aujourd'hui, la plupart des serveurs Web exécutent des services Web à l'aide d'une architecture Web sans état courant, appelée REST, qui signifie REpresentational StateTransfer. Les services Web qui proposent cette architecture sont appelés services RESTful.

Les requêtes sont envoyées aux services Web RESTful de manière standardisée via des URI. Un URI (Uniform Resource Identifier, ou identifiant de ressource uniforme) identifie une ressource sur le serveur grâce à son nom, sans indiquer son emplacement ni la manière d'y accéder. Par exemple, dans l'application de cette leçon, vous récupérez les URL des images à l'aide de l'URI de serveur suivant (ce serveur héberge à la fois les biens immobiliers sur Mars et des photos de Mars) :

android-kotlin-fun-mars-server.appspot.com

Une URL (Uniform Resource Locator, ou localisateur uniforme de ressource) est un URI qui spécifie le moyen d'agir ou d'obtenir la représentation d'une ressource, c'est-à-dire de spécifier à la fois son mécanisme d'accès principal et son emplacement réseau.

Par exemple :

L'URL suivante permet d'obtenir la liste de tous les biens immobiliers disponibles sur Mars.

https://android-kotlin-fun-mars-server.appspot.com/realestate

L'URL suivante permet d'obtenir une liste des photos de Mars :

https://android-kotlin-fun-mars-server.appspot.com/photos

Ces URL font référence à une ressource identifiée comme /realestate (bien immobilier) ou/photos, qui peut être obtenue via le protocole de transfert hypertexte (http:) du réseau. Dans cet atelier de programmation, vous utiliserez le point de terminaison /photos.

Requête de service Web

Chaque demande de service Web contient un URI et est transférée au serveur à l'aide du même protocole HTTP que celui utilisé par les navigateurs Web, comme Chrome. Les requêtes HTTP contiennent une opération indiquant au serveur quoi faire.

Les opérations HTTP courantes incluent :

  • GET pour récupérer les données du serveur ;
  • POST ou PUT pour l'ajout/la création/la mise à jour du serveur avec de nouvelles données ;
  • DELETE pour la suppression de données du serveur.

Votre application envoie une requête HTTP GET au serveur pour obtenir les informations sur les photos de Mars, puis le serveur renvoie une réponse à notre application qui comprend les URL des images.

bcd50e389186fa98.png

1e08dbc82558a7cd.png

La réponse d'un service Web est généralement mise en forme dans l'un des formats Web courants tels que XML ou JSON, des formats permettant de représenter des données structurées par paires clé-valeur. Vous en apprendrez davantage sur le format JSON dans la tâche suivante.

Dans cette tâche, vous allez établir une connexion réseau avec le serveur, communiquer avec celui-ci et recevoir une réponse JSON. Vous utiliserez un serveur backend déjà créé. Dans cet atelier de programmation, vous utiliserez la bibliothèque Retrofit, une bibliothèque tierce, pour communiquer avec le serveur backend.

Bibliothèques externes

Les bibliothèques externes ou les bibliothèques tierces sont comme des extensions des principales API d'Android. Elles sont principalement Open Source, développées par la communauté et alimentées grâce aux contributions de l'immense communauté Android à travers le monde. Cela permet aux développeurs Android comme vous de créer de meilleures applications.

bibliothèque Retrofit

La bibliothèque Retrofit que vous allez utiliser dans cet atelier de programmation pour communiquer avec le service Web RESTful de Mars est un bon exemple de bibliothèque bien gérée et alimentée. Vous pouvez le constater par vous-même en consultant les problèmes en cours (certains sont des demandes de fonctionnalités) et les problèmes fermés répertoriés sur la page GitHub. Si les développeurs résolvent les problèmes et répondent régulièrement aux demandes de fonctionnalités, cela signifie que cette bibliothèque est bien gérée et qu'elle peut être utilisée dans l'application. Une page de documentation Retrofit est aussi disponible.

La bibliothèque Retrofit communiquera avec le backend. Cela créera des URI pour le service Web en fonction des paramètres transmis. Vous en saurez plus dans les sections suivantes.

a8f10b735ad998ac.png

Ajouter des dépendances Retrofit

Android Gradle vous permet d'ajouter des bibliothèques externes à votre projet. En plus de la dépendance de la bibliothèque, vous devez également inclure le dépôt où elle est hébergée. Les bibliothèques Google, telles que ViewModel et LiveData, de la bibliothèque Jetpack sont hébergées dans le dépôt Google. La majorité des bibliothèques communautaires telles que Retrofit sont hébergées sur des dépôts Google et MavenCentral.

  1. Ouvrez le fichier build.gradle(Project: MarsPhotos) de premier niveau du projet. Notez les dépôts répertoriés sous le bloc repositories. Vous devriez voir deux dépôts, google() et mavenCentral().
repositories {
   google()
   mavenCentral()
}
  1. Ouvrez le fichier Gradle au niveau du module, build.gradle (Module: MarsPhots.app).
  2. Dans la section dependencies, ajoutez les lignes suivantes pour les bibliothèques Retrofit :
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Scalar Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"

La première dépendance est pour la bibliothèque Retrofit2 elle-même, et la seconde pour le convertisseur scalaire Retrofit. Ce convertisseur permet à Retrofit de renvoyer le résultat JSON sous forme de String. Les deux bibliothèques fonctionnent ensemble.

  1. Cliquez sur Synchroniser pour créer à nouveau le projet avec les nouvelles dépendances.

Ajouter la prise en charge des fonctionnalités du langage Java 8

De nombreuses bibliothèques tierces, y compris Retrofit2, utilisent les fonctionnalités du langage Java 8. Le plug-in Android Gradle est compatible avec l'utilisation de certaines fonctionnalités du langage Java 8.

  1. Pour utiliser les fonctionnalités intégrées, vous avez besoin du code suivant dans le fichier build.gradle de votre module. Cette étape a déjà été effectuée pour vous. Assurez-vous que le code suivant est présent dans votre build.gradle(Module: MarsPhotos.app).
android {
  ...

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

  kotlinOptions {
    jvmTarget = '1.8'
  }
}

6. Connexion à Internet

Vous utiliserez la bibliothèque Retrofit pour communiquer avec le service Web Mars et afficher la réponse JSON brute comme une String. L'espace réservé TextView affiche la chaîne de réponse JSON renvoyée ou un message indiquant une erreur de connexion.

Retrofit crée une API réseau pour l'application en fonction du contenu du service Web. Les données du service Web sont récupérées et acheminées via une bibliothèque de conversions distincte qui sait comment décoder les données et les renvoyer sous la forme d'objets tels que String. Cette fonctionnalité est compatible avec les formats de données courants, tels que XML et JSON. Au final, Retrofit crée le code qui appelle et utilise ce service à votre place, y compris les détails essentiels tels que l'exécution des requêtes sur des threads en arrière-plan.

deb437805232f6a.png

Dans cette tâche, vous allez ajouter à votre projet MarsPhotos une couche réseau que votre ViewModel utilisera pour communiquer avec le service Web. Suivez les prochaines étapes pour implémenter l'API de service Retrofit.

  • Créez une couche réseau, la classe MarsApiService.
  • Créez un objet Retrofit avec l'URL de base et la fabrique de conversions.
  • Créez une interface qui explique comment Retrofit communique avec notre serveur Web.
  • Créez un service Retrofit et exposez l'instance au service d'API pour le reste de l'application.

Procédez comme suit :

  1. Créez un package appelé "network" (réseau). Dans le volet de votre projet Android, effectuez un clic droit sur le package com.example.android.marsphotos. Sélectionnez Nouveau > Package. Dans la fenêtre pop-up, ajoutez network à la fin du nom de package suggéré.
  2. Créez un fichier Kotlin sous le nouveau package network. Nommez-le MarsApiService.
  3. Ouvrez network/MarsApiService.kt. Ajoutez la constante suivante pour l'URL de base du service Web.
private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. Juste en dessous de cette constante, ajoutez un compilateur Retrofit pour compiler et créer un objet Retrofit.
private val retrofit = Retrofit.Builder()

Importez retrofit2.Retrofit lorsque vous y êtes invité.

  1. Pour créer une API de services Web, Retrofit a besoin de l'URI de base du service Web et d'une fabrique de conversions. Le convertisseur indique à Retrofit comment traiter les données qu'il récupère auprès du service Web. Dans ce cas, vous souhaitez que Retrofit récupère une réponse JSON à partir du service Web et la renvoie sous forme de String. La mise à niveau comporte un ScalarsConverter (convertisseur scalaire) compatible avec les chaînes et autres types primitifs. Vous appelez donc addConverterFactory() sur le compilateur avec une instance de ScalarsConverterFactory.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())

Importez retrofit2.converter.scalars.ScalarsConverterFactory lorsque vous y êtes invité.

  1. Ajoutez l'URI de base pour le service Web à l'aide de la méthode baseUrl(). Enfin, appelez build() pour créer l'objet Retrofit.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()
  1. Sous l'appel du compilateur Retrofit, définissez une interface appelée MarsApiService, qui définit la façon dont Retrofit communique avec le serveur Web à l'aide de requêtes HTTP.
interface MarsApiService {
}
  1. Dans l'interface MarsApiService, ajoutez une fonction nommée getPhotos() pour obtenir la chaîne de réponse du service Web.
interface MarsApiService {
    fun getPhotos()
}
  1. Utilisez l'annotation @GET pour indiquer à Retrofit qu'il s'agit d'une requête GET et spécifier un point de terminaison pour cette méthode de service Web. Dans ce cas, le point de terminaison est appelé photos. Comme indiqué dans la tâche précédente, vous utiliserez le point de terminaison /photos dans cet atelier de programmation.
interface MarsApiService {
    @GET("photos")
    fun getPhotos()
}

Importez retrofit2.http.GET, si nécessaire.

  1. Lorsque la méthode getPhotos() est appelée, Retrofit ajoute le point de terminaison photos à l'URL de base (que vous avez définie dans le compilateur Retrofit) pour lancer la requête. Ajoutez un type renvoyé de la fonction à String.
interface MarsApiService {
    @GET("photos")
    fun getPhotos(): String
}

Déclarations d'objets

En Kotlin, les déclarations d'objets servent à déclarer des objets singleton. Le schéma singleton garantit qu'une seule et unique instance d'un objet est créée et qu'elle possède un point d'accès global à cet objet. L'initialisation de la déclaration d'objet est thread-safe et effectuée lors du premier accès.

Le langage Kotlin permet de déclarer facilement des singletons. Vous trouverez ci-dessous un exemple de déclaration d'objet et de son accès. Le nom de la déclaration d'objet est toujours suivi du mot clé object (objet).

Exemple :

// Object declaration
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
​
    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

// To refer to the object, use its name directly.
DataProviderManager.registerDataProvider(...)

L'appel de la fonction "create()" sur un objet Retrofit coûte cher, et l'application a besoin d'une seule instance du service de l'API Retrofit. Vous exposez donc le service au reste de l'application en utilisant la déclaration d'objet.

  1. En dehors de la déclaration d'interface de MarsApiService, définissez un objet public appelé MarsApi pour initialiser le service Retrofit. Il s'agit de l'objet singleton public accessible depuis le reste de l'application.
object MarsApi {

}
  1. Dans la déclaration d'objet MarsApi, ajoutez une propriété d'objet initialisée en différé retrofitService de type MarsApiService. Vous effectuez cette initialisation différée, pour vous assurer qu'elle est initialisée lors de sa première utilisation. Vous corrigerez l'erreur lors des prochaines étapes.
object MarsApi {
    val retrofitService : MarsApiService by lazy {
       }
}
  1. Initialisez la variable retrofitService à l'aide de la méthode retrofit.create() avec l'interface MarsApiService.
object MarsApi {
    val retrofitService : MarsApiService by lazy {
       retrofit.create(MarsApiService::class.java) }
}

La configuration de l'outil Retrofit est terminée. Chaque fois que votre application appelle MarsApi.retrofitService, l'appelant accède au même objet singleton Retrofit qui implémente MarsApiService, créé au premier accès. Dans la tâche suivante, vous allez utiliser l'objet Retrofit que vous avez implémenté.

Appeler le service Web dans OverviewViewModel

Au cours de cette étape, vous allez implémenter la méthode getMarsPhotos() qui appelle le service de réajustement, puis gère la chaîne JSON renvoyée.

ViewModelScope

Le ViewModelScope est le champ d'application de la coroutine intégrée qui est défini pour chaque ViewModel dans votre application. Toute coroutine lancée dans ce champ d'application est automatiquement annulée si le ViewModel est effacé.

Vous allez utiliser ViewModelScope pour lancer la coroutine et effectuer l'appel réseau Retrofit en arrière-plan.

  1. Dans MarsApiService, faites de getPhotos() une fonction de suspension. Vous pouvez appeler cette méthode à partir d'une coroutine.
@GET("photos")
suspend fun getPhotos(): String
  1. Ouvrez overview/OverviewViewModel. Faites défiler la page jusqu'à la méthode getMarsPhotos(). Supprimez la ligne qui définit la réponse de l'état sur "Set the Mars API Response here!".. La méthode getMarsPhotos() doit maintenant être vide.
private fun getMarsPhotos() {

}
  1. Dans getMarsPhotos(), lancez la coroutine à l'aide de viewModelScope.launch.
private fun getMarsPhotos() {
    viewModelScope.launch {
    }
}

Importez androidx.lifecycle.viewModelScope et kotlinx.coroutines.launch lorsque vous y êtes invité.

  1. Dans viewModelScope, utilisez l'objet singleton MarsApi pour appeler la méthode getPhotos() à partir de l'interface retrofitService. Enregistrez la réponse renvoyée dans un val appelé listResult.
viewModelScope.launch {
    val listResult = MarsApi.retrofitService.getPhotos()
}

Importez com.example.android.marsphotos.network.MarsApi lorsque vous y êtes invité.

  1. Attribuez le résultat que nous venons de recevoir du serveur backend à _status.value..
 val listResult = MarsApi.retrofitService.getPhotos()
 _status.value = listResult
  1. Exécutez l'application. Notez qu'elle se ferme immédiatement et qu'un message d'erreur peut s'afficher ou non.
  2. Cliquez sur l'onglet Logcat dans Android Studio et intéressez-vous à l'erreur dans le journal, qui commence par une ligne semblable à celle-ci : "------- beginning of crash".
    --------- beginning of crash
22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.example.android.marsphotos, PID: 22803
    java.lang.SecurityException: Permission denied (missing INTERNET permission?)
...

Ce message d'erreur indique que l'application ne dispose peut-être pas des autorisations INTERNET. Pour résoudre ce problème, ajoutez des autorisations Internet à l'application à la prochaine tâche.

7. Ajouter l'autorisation Internet et la gestion des exceptions

Autorisations Android

L'objectif des autorisations sur Android est de protéger la vie privée d'un utilisateur Android. Les applications Android doivent déclarer ou demander des autorisations pour accéder aux données utilisateur sensibles telles que les contacts, les journaux d'appels et certaines fonctionnalités du système, comme l'appareil photo ou Internet.

Pour que votre application accède à Internet, l'autorisation INTERNET est requise. La connexion à Internet pose un problème de sécurité, c'est pourquoi les applications n'ont pas de connexion Internet par défaut. Vous devez déclarer explicitement que l'application a besoin d'accéder à Internet. Cette autorisation est considérée comme normale. Pour en savoir plus sur les autorisations Android et leurs types, consultez la documentation.

À cette étape, votre application déclare la ou les autorisations requises en incluant des balises <uses-permission> dans le fichier AndroidManifest.

  1. Ouvrez manifests/AndroidManifest.xml. Ajoutez cette ligne juste avant la balise <application> :
<uses-permission android:name="android.permission.INTERNET" />
  1. Compilez et exécutez à nouveau l'application. Si votre connexion Internet fonctionne, vous devriez voir le texte JSON contenant les données des photos de Mars. Vous en apprendrez plus sur le format JSON plus tard dans cet atelier de programmation.

205710014543679a.png

  1. Appuyez sur le bouton Retour de votre appareil ou de votre émulateur pour fermer l'application.
  2. Activez le mode Avion de votre appareil ou émulateur pour simuler une erreur de connexion réseau. Rouvrez l'application depuis le menu "Applications récentes" ou redémarrez-la depuis Android Studio.
  3. Cliquez sur l'onglet Logcat dans Android Studio et notez l'exception fatale dans le journal :
3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.android.marsphotos, PID: 3302
    java.net.SocketTimeoutException: timeout
...

Ce message d'erreur indique que l'application a essayé de se connecter et que le délai a expiré. De telles exceptions sont très courantes en temps réel. À l'étape suivante, vous découvrirez comment gérer ces exceptions.

Gestion des exceptions

Les exceptions sont des erreurs qui peuvent se produire pendant l'exécution (et non au moment de la compilation), et entraîner l'arrêt brutal de l'application sans en avertir l'utilisateur. Les exceptions peuvent nuire à l'expérience utilisateur. La gestion des exceptions est un mécanisme qui vous permet d'empêcher l'application de s'arrêter brusquement et de la gérer facilement.

Le motif des exceptions peut être une division par zéro ou une erreur dans le réseau. Ces exceptions sont semblables aux NumberFormatException que vous avez apprises dans un atelier de programmation précédent.

Exemples de problèmes potentiels lors de la connexion à un serveur :

  • L'URL ou l'URI utilisés dans l'API sont incorrects.
  • Le serveur est indisponible et l'application n'a pas pu s'y connecter.
  • Problème de latence du réseau.
  • Connexion Internet de mauvaise qualité ou inexistante sur l'appareil.

Ces exceptions ne peuvent pas être détectées pendant la compilation. Vous pouvez utiliser un bloc try-catch pour gérer l'exception au moment de l'exécution. Pour en savoir plus, consultez la documentation.

Exemple de syntaxe pour un bloc try-catch

try {
    // some code that can cause an exception.
}
catch (e: SomeException) {
    // handle the exception to avoid abrupt termination.
}

Dans le bloc try, vous exécutez le code pour lequel vous prévoyez une exception. Dans votre application, il s'agit d'un appel réseau. Dans le bloc catch, vous allez implémenter le code qui empêche l'arrêt brutal de l'application. En cas d'exception, le bloc catch sera exécuté pour récupérer de l'erreur au lieu d'arrêter brutalement l'application.

  1. Ouvrez overview/OverviewViewModel.kt. Faites défiler la page jusqu'à la méthode getMarsPhotos(). Dans le bloc de lancement, ajoutez un bloc try autour de l'appel MarsApi pour gérer les exceptions. Ajoutez le bloc catch après le bloc try :
viewModelScope.launch {
   try {
       val listResult = MarsApi.retrofitService.getPhotos()
       _status.value = listResult
   } catch (e: Exception) {

   }
}
  1. Dans le bloc catch {}, gérez la réponse de l'échec. Affichez le message d'erreur pour l'utilisateur en définissant e.message sur _status.value.
catch (e: Exception) {
   _status.value = "Failure: ${e.message}"
}
  1. Exécutez à nouveau l'application, avec le mode Avion activé. Cette fois, l'application ne se ferme pas brusquement, mais un message d'erreur s'affiche à la place.

368c746124f57a93.png

  1. Désactivez le mode Avion sur votre téléphone ou dans votre émulateur. Exécutez et testez votre application, assurez-vous que tout fonctionne correctement et que vous pouvez voir la chaîne JSON.

8. Analyser la réponse JSON avec Moshi

JSON

Les données demandées sont généralement formatées dans l'un des formats de données courants tels que XML ou JSON. Chaque appel renvoie des données structurées, et votre application doit connaître cette structure pour lire les données de la réponse.

Par exemple, dans l'application suivante, vous allez récupérer les données de ce serveur : https:// android-kotlin-fun-mars-server.appspot.com/photos. Si vous saisissez cette URL dans le navigateur, vous verrez une liste d'ID et d'URL d'images de la surface de Mars au format JSON.

Structure d'une partie d'une réponse JSON :

fde4f6f199990ae8.png

  • La réponse JSON est un tableau, indiqué entre crochets. Le tableau contient des objets JSON.
  • Les objets JSON sont entourés d'accolades.
  • Chaque objet JSON contient un ensemble de paires nom/valeur séparées par une virgule.
  • Le nom et la valeur dans une paire sont séparés par deux-points.
  • Les noms sont entourés de guillemets.
  • Les valeurs peuvent être des nombres, des chaînes, des valeurs booléennes, un tableau, un objet (objet JSON) ou la valeur "null".

Par exemple, img_src est une URL, qui est une chaîne. Si vous collez l'URL dans un navigateur Web, une image de la surface de Mars s'affiche.

b4f9f196c64f02c3.png

Vous recevez maintenant une réponse JSON du service Web Mars, ce qui est un bon début. Mais, en réalité, vous avez besoin d'objets Kotlin, et non d'une grande chaîne JSON. Il existe une bibliothèque externe appelée Moshi, qui est un analyseur JSON Android qui convertit une chaîne JSON en objets Kotlin. Le convertisseur Retrofit est compatible avec Moshi, ce qui en fait une bibliothèque idéale pour notre exercice.

Dans cette tâche, vous allez utiliser la bibliothèque Moshi avec Retrofit pour analyser la réponse JSON du service Web dans des objets Kotlin utiles qui représentent des photos de Mars. Vous modifierez l'application de sorte qu'au lieu d'afficher le fichier JSON brut, l'application affiche le nombre de photos de Mars renvoyées.

Ajouter des dépendances de la bibliothèque Moshi

  1. Ouvrez build.gradle (Module: app).
  2. Dans la section sur les dépendances, ajoutez le code ci-dessous pour inclure la dépendance Moshi. Cette dépendance est compatible avec la bibliothèque JSON de Moshi, qui est compatible avec Kotlin.
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
  1. Recherchez les lignes du convertisseur scalaire Retrofit dans le bloc dependencies et modifiez ces dépendances pour utiliser converter-moshi :

Remplacez cet extrait :

// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with scalar Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"

Par cet extrait :

// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
  1. Cliquez sur Synchroniser pour créer à nouveau le projet avec les nouvelles dépendances.

Implémenter la classe de données Mars Photos

Un échantillon d'entrée de réponse JSON provenant du service Web ressemble à ceci (cela ressemble beaucoup à ce que nous avons vu auparavant) :

[{
    "id":"424906",
    "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
},
...]

Dans l'exemple ci-dessus, notez que chaque entrée de photo sur Mars comporte les paires clé/valeur suivantes :

  • id : ID de la propriété, sous forme de chaîne. Étant donné qu'il est entre " ", il est de type String et non Integer.
  • img_src : URL de l'image sous forme de chaîne.

Moshi analyse ces données JSON et les convertit en objets Kotlin. Pour ce faire, Moshi doit disposer d'une classe de données Kotlin pour stocker les résultats analysés. Dans cette étape, vous allez créer la classe de données MarsPhoto.

  1. Effectuez un clic droit sur le package network et sélectionnez Nouveau > Fichier/Classe Kotlin.
  2. Dans le pop-up, sélectionnez Classe, puis saisissez MarsPhoto comme nom de classe. Cette opération crée un fichier nommé MarsPhoto.kt dans le package network.
  3. Transformez MarsPhoto en classe de données en ajoutant le mot clé data avant la définition de classe. Remplacez les accolades {} par des parenthèses (). Vous obtiendrez une erreur, car au moins une propriété doit être définie pour les classes de données.
data class MarsPhoto(
)
  1. Ajoutez les propriétés suivantes à la définition de la classe MarsPhoto.
data class MarsPhoto(
   val id: String, val img_src: String
)

Notez que chacune des variables de la classe MarsPhoto correspond à un nom de clé dans l'objet JSON. Pour faire correspondre les types à notre réponse JSON, utilisez des objets String pour toutes les valeurs.

Lorsque Moshi analyse le fichier JSON, il met en correspondance les clés en fonction des noms et remplit les objets de données avec les bonnes valeurs.

@Json Annotation

Dans les réponses JSON, les noms de clés peuvent parfois rendre les propriétés Kotlin déroutantes, ou ne pas correspondre au style de codage recommandé. Par exemple, dans le fichier JSON, la clé img_src utilise un underscore (ou tiret du bas), tandis que la convention Kotlin pour les propriétés utilise des lettres majuscules et minuscules ("camel case").

Pour utiliser dans votre classe de données des noms de variables différents des noms de clés de la réponse JSON, utilisez l'annotation @Json. Dans cet exemple, la variable de la classe de données s'appelle imgSrcUrl. La variable peut être mappée avec l'attribut JSON img_src à l'aide de @Json(name = "img_src").

  1. Remplacez la ligne de la clé img_src par celle indiquée ci-dessous. Importez com.squareup.moshi.Json, si nécessaire.
@Json(name = "img_src") val imgSrcUrl: String

Mettre à jour MarsApiService et OverviewViewModel

Dans cette tâche, vous créerez un objet Moshi à l'aide du Moshi Builder, semblable au constructeur Retrofit.

Vous remplacerez ScalarsConverterFactory par KotlinJsonAdapterFactory pour indiquer à Retrofit qu'il peut utiliser Moshi pour convertir la réponse JSON en objets Kotlin. Vous allez ensuite mettre à jour l'API réseau et ViewModel pour utiliser l'objet Moshi.

  1. Ouvrez network/MarsApiService.kt. Observez les erreurs de référence non résolues pour ScalarsConverterFactory. Cela est dû à la modification de la dépendance Retrofit que vous avez effectuée lors d'une étape précédente. Supprimez l'importation pour ScalarConverterFactory. Vous corrigerez bientôt la seconde erreur.

Suppression :

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. En haut du fichier, juste avant le compilateur Retrofit, ajoutez le code suivant pour créer l'objet Moshi, semblable à l'objet Retrofit.
private val moshi = Moshi.Builder()

Importez com.squareup.moshi.Moshi et com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory lorsque vous y êtes invité.

  1. Pour que les annotations de Moshi fonctionnent correctement avec Kotlin, ajoutez KotlinJsonAdapterFactory dans le compilateur Moshi, puis appelez build().
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()
  1. Dans la déclaration d'objet retrofit, modifiez le compilateur Retrofit pour utiliser MoshiConverterFactory au lieu de ScalarConverterFactory, et transmettez l'instance moshi que vous venez de créer.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()

Importez retrofit2.converter.moshi.MoshiConverterFactory, si nécessaire.

  1. Maintenant que MoshiConverterFactory est en place, vous pouvez demander à Retrofit de renvoyer une liste d'objets MarsPhoto depuis le tableau JSON au lieu de renvoyer une chaîne JSON. Mettez à jour l'interface MarsApiService pour que Retrofit renvoie une liste d'objets MarsPhoto au lieu de renvoyer une String.
interface MarsApiService {
   @GET("photos")
   suspend fun getPhotos(): List<MarsPhoto>
}
  1. Effectuez des modifications similaires dans viewModel, ouvrez OverviewViewModel.kt. Faites défiler la page jusqu'à la méthode getMarsPhotos().
  2. Dans la méthode getMarsPhotos(), listResult est un List<MarsPhoto>, et non une String. La taille de cette liste correspond au nombre de photos reçues et analysées. Pour afficher le nombre de photos récupérées, mettez à jour _status.value comme suit.
_status.value = "Success: ${listResult.size} Mars photos retrieved"

Importez com.example.android.marsphotos.network.MarsPhoto lorsque vous y êtes invité.

  1. Assurez-vous que le mode Avion est désactivé sur votre appareil ou votre émulateur. Compilez et exécutez l'application. Cette fois, le message devrait indiquer le nombre de propriétés renvoyées par le service Web (il ne s'agit pas d'une grande chaîne JSON) :

8f47f004c7f91394.png

9. Code de solution

build.gradle(Module: MarsPhotos.app)

Voici les nouvelles dépendances à inclure.

dependencies {
    ...
    // Moshi
    implementation 'com.squareup.moshi:moshi-kotlin:1.13.0'

    // Retrofit with Moshi Converter
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

    ...
}

Manifests/AndroidManifest.xml

Ajoutez l'autorisation Internet (le code <uses-permission..> dans l'extrait de code ci-dessous).

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.marsphotos">

    <!-- In order for our app to access the Internet, we need to define this permission. -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        ...
    </application>

</manifest>

network/MarsPhoto.kt

package com.example.android.marsphotos.network

import com.squareup.moshi.Json

/**
* This data class defines a Mars photo which includes an ID, and the image URL.
* The property names of this data class are used by Moshi to match the names of values in JSON.
*/
data class MarsPhoto(
   val id: String,
   @Json(name = "img_src") val imgSrcUrl: String
)

network/MarsApiService.kt

package com.example.android.marsphotos.network

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"

/**
* Build the Moshi object with Kotlin adapter factory that Retrofit will be using.
*/
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

/**
* The Retrofit object with the Moshi converter.
*/
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()

/**
* A public interface that exposes the [getPhotos] method
*/
interface MarsApiService {
   /**
    * Returns a [List] of [MarsPhoto] and this method can be called from a Coroutine.
    * The @GET annotation indicates that the "photos" endpoint will be requested with the GET
    * HTTP method
    */
   @GET("photos")
   suspend fun getPhotos() : List<MarsPhoto>
}

/**
* A public Api object that exposes the lazy-initialized Retrofit service
*/
object MarsApi {
   val retrofitService: MarsApiService by lazy { retrofit.create(MarsApiService::class.java) }
}

Overview/OverviewViewModel.kt

package com.example.android.marsphotos.overview

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.android.marsphotos.network.MarsApi
import kotlinx.coroutines.launch

/**
* The [ViewModel] that is attached to the [OverviewFragment].
*/
class OverviewViewModel : ViewModel() {

   // The internal MutableLiveData that stores the status of the most recent request
   private val _status = MutableLiveData<String>()

   // The external immutable LiveData for the request status
   val status: LiveData<String> = _status
   /**
    * Call getMarsPhotos() on init so we can display status immediately.
    */
   init {
       getMarsPhotos()
   }

   /**
    * Gets Mars photos information from the Mars API Retrofit service and updates the
    * [MarsPhoto] [List] [LiveData].
    */
   private fun getMarsPhotos() {
       viewModelScope.launch {
           try {
               val listResult = MarsApi.retrofitService.getPhotos()
               _status.value = "Success: ${listResult.size} Mars photos retrieved"
           } catch (e: Exception) {
               _status.value = "Failure: ${e.message}"
           }
       }
   }
}

10. Résumé

Services Web REST

  • Un service Web est une fonctionnalité logicielle sur Internet qui permet à votre application d'effectuer des requêtes et d'obtenir des données.
  • Les services Web courants utilisent une architecture REST. Les services Web qui proposent une architecture REST sont appelés services RESTful. Les services Web RESTful sont conçus à l'aide de protocoles et de composants Web standards.
  • Vous envoyez une requête à un service Web REST de manière standardisée, via des URI.
  • Pour utiliser un service Web, une application doit établir une connexion réseau et communiquer avec le service. L'application doit ensuite recevoir et analyser les données de réponse dans un format qu'elle peut utiliser.
  • La bibliothèque Retrofit est une bibliothèque cliente qui permet à votre application d'envoyer des requêtes à un service Web REST.
  • Utilisez des convertisseurs pour indiquer à Retrofit comment traiter les données qu'il envoie au service Web et qu'il récupère. Par exemple, le convertisseur ScalarsConverter traite les données du service Web comme une String ou tout autre type primitif.
  • Pour autoriser votre application à établir une connexion à Internet, ajoutez l'autorisation "android.permission.INTERNET" dans le fichier manifeste Android.

Analyse JSON

  • La réponse d'un service Web est souvent au format JSON, un format courant pour représenter des données structurées.
  • Un objet JSON est une collection de paires clé-valeur.
  • Une collection d'objets JSON est un tableau JSON. Vous obtenez un tableau JSON en tant que réponse d'un service Web.
  • Les clés d'une paire clé-valeur sont comprises entre guillemets. Les valeurs peuvent être des nombres ou des chaînes.
  • La bibliothèque Moshi est un analyseur JSON Android qui convertit une chaîne JSON en objets Kotlin. Le convertisseur Retrofit est compatible avec Moshi.
  • Moshi met en correspondance les clés d'une réponse JSON avec les propriétés d'un objet de données portant le même nom.
  • Pour utiliser un nom de propriété différent pour une clé, annotez cette propriété avec @Json et le nom de la clé JSON.

11. En savoir plus

Documentation pour les développeurs Android :

Documentation Kotlin :

Autre :