• 10 hours
  • Easy

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 3/12/24

Rendez une application fluide

Évitez que votre application freeze

Vous recevez un nouveau message de Margaret pour vous expliquer la suite de la mission :

Bonjour, 

Un point essentiel dont nous discuterons lors de cette phase concerne la stabilité et la fluidité de l'application. Nous aborderons comment éviter les plantages et rendre l'expérience utilisateur aussi fluide que possible grâce à l'utilisation de coroutines.

En parallèle, nous travaillerons sur l'interface utilisateur de l'application. Adele, notre spécialiste en design d'interfaces, fournira les éléments nécessaires pour créer une interface conviviale. L'objectif est de permettre aux utilisateurs de visualiser rapidement les conditions météorologiques et de planifier leurs séances d'observation.

Cordialement,

Margaret Hamilton, Dev Senior – Projet "Planète Exploration"

Dans ce nouveau chapitre, nous allons collecter et afficher les informations dans l’application.

Si nous reprenons notre exemple du serveur qui va chercher vos commandes en cuisine, il y a un aspect que nous n’avons pas traité : que fait le client pendant ce temps ? Il patiente.

Dans notre contexte, une application mobile qui patiente est une application qui gèle/freeze. Elle ne fonctionne plus et l’OS Android vous propose de la fermer/tuer (kill). En tant que développeur consciencieux, nous ne voulons pas de ça. Par conséquent, nous allons utiliser des méthodes asynchrones pour éviter cet inconvénient.

Découvrez les coroutines

En programmation, une coroutine peut être vue comme une unité de travail qui permet d'effectuer des tâches de manière asynchrone sans bloquer le fil d'exécution principal de l'application. Les coroutines sont des structures légères et flexibles, facilitant la gestion des opérations longues sans sacrifier la lisibilité du code.

La gestion asynchrone est cruciale dans le développement d'applications Android pour éviter les retards et les gels de l'interface utilisateur. Les coroutines offrent une alternative moderne et élégante aux approches traditionnelles telles que la gestion manuelle de callbacks, AsyncTask, ou encore l'utilisation de threads qui peuvent être gourmands en ressources.

Par exemple, dans le développement d'applications, on peut avoir un "Thread UI" qui gère l'interface utilisateur, assurant une réponse fluide aux interactions de l'utilisateur, tandis qu'un "Thread réseau" s'occupe des opérations de communication avec un serveur distant, comme le chargement de données.

L'utilisation de threads distincts améliore la réactivité de l'application en évitant que des tâches intensives bloquent l'interface utilisateur, assurant ainsi une expérience utilisateur plus fluide.

Le multithreading comparé au single threading
Le multithreading comparé au single threading

Utilisez une méthode asynchrone

Pour mieux comprendre l'utilité d'une méthode asynchrone à l'aide des coroutines, examinons d'abord un contre-exemple. Copiez le code suivant dans votre MainActivity, puis lancez l'application. Vous remarquerez qu'Android vous propose de fermer l'application en raison du gel de la méthode suivante :

for (i in 1..10000) {
Thread.sleep(5)
}

Le gel se produit parce que la méthode effectue un traitement long et bloquant directement sur le thread principal, ce qui provoque un gel de l'interface utilisateur. On vous propose de tuer l’application. Android, par défaut, utilise toujours le thread principal si on ne spécifie rien.

Maintenant, pour résoudre ce problème, nous allons transformer cette méthode en une version asynchrone en utilisant des coroutines. Refaites la même manipulation que précédemment avec le code suivant, et vous constaterez que vous obtenez ce résultat dans les logs cette fois-ci :

GlobalScope.launch(Dispatchers.IO) {
   for (i in 1..10000) {
Thread.sleep(5)
}
   println("Sleepy function done")
}

En utilisant le contexte  Dispatchers.IO  , le traitement long s'effectue dans un thread dédié au réseau, laissant le thread principal libre pour gérer les interactions de l'interface utilisateur. Ainsi, l'application reste réactive et ne se fige pas pendant le traitement asynchrone.

L'application peut utiliser le thread UI/principal pour dessiner la vue sans être bloquée, assurant ainsi une meilleure expérience utilisateur.

La création d’une coroutine sur le thread réseau nous permet d’attendre le retour de notre requête sans bloquer l’application. Par défaut, Retrofit utilise une coroutine sur le thread réseau, nous n’aurons donc pas sa déclaration à faire de notre côté.

Découvrez l’utilité d’un repository

À cette étape, nous allons créer un repository, un composant essentiel de l'architecture logicielle de notre application Android.

Le repository présente plusieurs avantages pour la gestion des données dans une application Android :

  • Il simplifie l'accès aux différentes sources de données en fournissant des méthodes retournant des données. 

  • Il facilite l'implémentation de la logique métier, en isolant le  ViewModel  de la complexité des opérations sur les données. 

  • Il est responsable de la qualité des données que nous transmettons à notre  ViewModel  ; en effet, vu que nous pouvons avoir plusieurs sources (locales ou en ligne), le jeu de données peut changer. On utilisera donc notre objet  DomainModel  ici pour être sûr de ce que reçoit notre  ViewModel  .

Le repository sera donc notre magasin où nous stockerons temporairement nos produits (données). Les clients (ViewModels) peuvent se rendre au magasin pour obtenir ce dont ils ont besoin, sans avoir à se soucier des détails internes de gestion des stocks. Est-ce que les données sont présentes localement ou en ligne ? Le ViewModel ne s’en inquiète pas, c’est au repository de gérer les données. Avec notre application météo, nous ne faisons que des interactions en ligne, mais si besoin, c’est dans le repository qu’il faudrait faire appel au cache, par exemple.

Mettez en place un repository

Pour illustrer la mise en œuvre d'un repository, commençons par définir une méthode qui utilise le concept de flow (flux), une fonctionnalité avancée de Kotlin Coroutine. Cela nous permettra de gérer de manière asynchrone les flux de données, ce qui est particulièrement utile pour les opérations qui peuvent prendre du temps, comme les requêtes réseau. Flow est une coroutine qui permet d'émettre des données en continu si besoin.

Créons une classe nommée  data.repository.WeatherRepository  , comme nous en avons déjà créé auparavant.

Création d'une nouvelle classe
Création d'une nouvelle classe
import android.util.Log
import com.openclassrooms.stellarforecast.data.network.WeatherClient
import com.openclassrooms.stellarforecast.domain.model.WeatherReportModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
 
class WeatherRepository(private val dataService: WeatherClient) {
private val API_KEY = "REPLACE WITH YOUR API KEY HERE"
 
   fun fetchForecastData(latouble, lngouble): Flow<List<WeatherReportModel>> = flow {
       val result = dataService.getWeatherByPosition(lat, lng, API_KEY)
val model = result.body()?.toDomainModel() ?: throw Exception("Invalid data")
 
emit(model)
   }.catch { error ->
       Log.e("WeatherRepository", error.message ?: "")
   }
}

Voici en détail ce que vous pouvez lire dans ce snippet de code :

  • Rôle du  WeatherRepository  :

    Agit comme un gardien des prévisions météorologiques, interagissant avec Retrofit et donc notre API pour récupérer ces données et les rendre accessibles à d'autres parties de l'application.

    Définit la clé API nécessaire pour l'interrogation du service météorologique distant.

  • Fonction  fetchForecastData  :

    Prend en paramètres la latitude (lat) et la longitude (lng) pour spécifier l'emplacement désiré.

    Utilise un flux (flow) pour gérer les données de manière asynchrone, créant ainsi une coroutine. À chaque fois qu’une nouvelle donnée sera disponible, nous n’aurons qu'à émettre avec la fonction “emit” propre au “flow”.

  • Séquence d'instructions dans la  flow  :

    Utilise la clé API pour demander les prévisions météorologiques au  dataService  , qui représente le serveur distant.

    Traite le résultat : transforme les données en un modèle adapté à l'application à l'aide de  toDomainModel()  .

    Émet ce modèle dans la  flow  avec  emit  .

  • Gestion des erreurs :

    Utilise  catch  pour gérer toute exception potentielle.

    Affiche un message d'erreur dans le journal en cas de problème ; nous emploierons cette gestion des erreurs dans un prochain chapitre.

En résumé, le  WeatherRepository  assure une communication entre votre ViewModel et le service météorologique distant, garantissant que les prévisions météorologiques sont récupérées et traitées correctement, même en cas d'obstacles sur le chemin.

Pour permettre à Hilt de configurer automatiquement notre repository, nous allons créer un nouveau module, de la même manière que nous l'avons fait pour Retrofit. Créez une classe nommée  di.DataModule  et copiez le code suivant :

import com.openclassrooms.stellarforecast.data.network.WeatherClient
import com.openclassrooms.stellarforecast.data.repository.WeatherRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
 
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
 
@Singleton
@Provides
fun provideWeatherRepository(dataClient: WeatherClient): WeatherRepository {
return WeatherRepository(dataClient)
}
}

Étant donné que nous avons déjà défini  WeatherClient  avec Hilt, il sera en mesure de le fournir à  WeatherRepository  et de créer l'objet en conséquence.

À vous de jouer

Contexte

Alors que vous contemplez à travers la fenêtre les premiers flocons de neige de l'année, une sensation de fraîcheur hivernale envahit l'air.

La saison tant attendue est enfin arrivée ! (Si malheureusement, nous sommes en plein été, imaginez-vous en Antarctique :)).

Avec l'arrivée de cette nouvelle période, il est impératif de se concentrer sur la gestion des données qui seront nécessaires pour créer une interface utilisateur informative.

Pour cela, la classe  Repository  , notre sauveur de données, doit être définie. Elle jouera un rôle crucial dans la récupération des informations essentielles, permettant ainsi de concrétiser la conception de la vue tant désirée.

Consignes

Votre mission actuelle consiste à :

  • définir une classe  Repository  .

Corrigé

Le corrigé est disponible sur GitHub à cette adresse

  • Déclaration du  Repository  .

En résumé

  • Les coroutines sont des structures légères permettant la gestion asynchrone du code. Elles simplifient le travail avec des tâches longues et des opérations asynchrones sans le fardeau de la gestion manuelle des threads.

  • Les coroutines, telles que  GlobalScope.launch(Dispatchers.IO)  , offrent une gestion asynchrone efficace en nous permettant de sélectionner notre thread, évitant le gel de l'application. Retrofit utilise ces coroutines.

  • L'utilisation de threads distincts, tels que le "Thread UI" et le "Thread réseau", optimise la réactivité de l'application.

  • Le repository simplifie l'accès aux données avec des méthodes améliorant la logique métier dans l'application Android. La méthode  fetchForecastData()  utilise des coroutines pour récupérer de manière asynchrone les prévisions météorologiques, préservant ainsi la réactivité de l'interface utilisateur.

Nous en avons terminé avec la configuration et la gestion des données ; orientons-nous vers le ViewModel qui nous permettra de nourrir notre interface graphique.

Example of certificate of achievement
Example of certificate of achievement