1. Avant de commencer
Dans l'atelier de programmation précédent, vous avez découvert les coroutines. Vous avez utilisé Kotlin Playground pour écrire du code simultané à l'aide de coroutines. Dans cet atelier de programmation, vous appliquerez vos connaissances sur les coroutines dans une application Android et son cycle de vie. Vous ajouterez du code pour lancer de nouvelles coroutines simultanément et découvrirez comment les tester.
Conditions préalables
- Vous connaissez les bases de la programmation en Kotlin, y compris les fonctions et les lambdas.
- Vous êtes capable de créer des mises en page dans Jetpack Compose.
- Vous pouvez écrire des tests unitaires en Kotlin (consultez l'atelier de programmation Écrire des tests unitaires pour ViewModel).
- Vous savez comment fonctionnent les threads et la simultanéité.
- Vous disposez de connaissances de base sur les coroutines et CoroutineScope.
Objectifs de l'atelier
- Créez une application de suivi de course, nommée Race Tracker, qui simule la progression d'une course entre deux joueurs. Considérez l'application comme une occasion d'expérimenter et d'en apprendre davantage sur les différents aspects des coroutines.
Points abordés
- Utiliser des coroutines dans le cycle de vie d'une application Android
- Principes de la simultanéité structurée
- Écrire des tests unitaires pour tester les coroutines
Ce dont vous avez besoin
- La dernière version stable d'Android Studio
2. Présentation de l'application
L'application Race Tracker simule une course entre deux joueurs. Son interface utilisateur est composée des boutons Start (Commencer)/Pause (Mettre en pause) et Reset (Réinitialiser), ainsi que de deux barres de progression indiquant la progression des coureurs. Les joueurs 1 et 2 sont programmés pour courir à des vitesses différentes. Au début de la course, le joueur 2 progresse deux fois plus vite que le joueur 1.
Vous utiliserez les coroutines dans l'application pour vous assurer que :
- les deux joueurs font la course simultanément ;
- l'interface utilisateur de l'application est responsive, et que les barres de progression s'incrémentent pendant la course.
Le code de démarrage contient le code d'interface utilisateur de l'application Race Tracker. L'objectif principal de cet atelier de programmation est de vous familiariser avec les coroutines Kotlin dans une application Android.
Obtenir le code de démarrage
Pour commencer, téléchargez le code de démarrage :
Vous pouvez également cloner le dépôt GitHub du code :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-race-tracker.git $ cd basic-android-kotlin-compose-training-race-tracker $ git checkout starter
Vous pouvez parcourir le code de démarrage dans le dépôt GitHub de Race Tracker.
Tutoriel du code de démarrage
Vous pouvez lancer la course en cliquant sur le bouton Start (Démarrer). Pendant la course, le texte du bouton Start (Démarrer) devient Pause (Mettre en pause).
Vous pouvez utiliser ce bouton à tout moment pour interrompre ou reprendre la course.
Au début de la course, un indicateur d'état affiche la progression de chaque joueur. La fonction modulable StatusIndicator
affiche l'état de progression de chaque joueur. Il utilise le composable LinearProgressIndicator
pour afficher la barre de progression. Vous utiliserez des coroutines pour mettre à jour la valeur de la progression.
RaceParticipant
fournit les données de l'incrément de progression. Cette classe est un conteneur d'état pour chacun des joueurs et gère le name
du coureur, le maxProgress
à atteindre pour terminer la course, le délai entre chaque incrément, le currentProgress
de la course et le initialProgress
.
Dans la section suivante, vous utiliserez des coroutines pour implémenter la fonctionnalité permettant de simuler la progression de la course sans bloquer l'interface utilisateur de l'application.
3. Implémenter la progression de la course
Vous avez besoin de la fonction run()
qui compare le currentProgress
du joueur contre le maxProgress
, qui reflète la progression totale de la course et utilise la fonction de suspension delay()
pour ajouter un léger décalage entre les incréments de progression. Il s'agit d'une fonction suspend
, car elle appelle une autre fonction de suspension delay()
. Dans la suite de cet atelier de programmation, vous l'appellerez également à partir d'une coroutine. Pour implémenter la fonction, procédez comme suit :
- Ouvrez la classe
RaceParticipant
, qui est fournie dans le code de démarrage. - Dans la classe
RaceParticipant
, définissez une nouvelle fonctionsuspend
nomméerun()
.
class RaceParticipant(
...
) {
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
}
...
}
- Pour simuler la progression de la course, ajoutez une boucle
while
qui s'exécute jusqu'à ce quecurrentProgress
atteigne la valeurmaxProgress
, qui est définie sur100
.
class RaceParticipant(
...
val maxProgress: Int = 100,
...
) {
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
while (currentProgress < maxProgress) {
}
}
...
}
- La valeur de
currentProgress
est définie surinitialProgress
, soit0
. Pour simuler la progression du coureur, augmentez la valeurcurrentProgress
de la valeur de la propriétéprogressIncrement
dans la boucle "while". Notez que la valeur par défaut deprogressIncrement
est1
.
class RaceParticipant(
...
val maxProgress: Int = 100,
...
private val progressIncrement: Int = 1,
private val initialProgress: Int = 0
) {
...
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
while (currentProgress < maxProgress) {
currentProgress += progressIncrement
}
}
}
- Pour simuler différents intervalles de progression dans la course, utilisez la fonction de suspension
delay()
. Transmettez la valeur de la propriétéprogressDelayMillis
en tant qu'argument.
suspend fun run() {
while (currentProgress < maxProgress) {
delay(progressDelayMillis)
currentProgress += progressIncrement
}
}
Si vous examinez le code que vous venez d'ajouter, vous verrez une icône à gauche de l'appel à la fonction delay()
dans Android Studio, comme le montre la capture d'écran ci-dessous : .
Cette icône indique le point de suspension où la fonction peut être suspendue et reprise ultérieurement.
Le thread principal n'est pas bloqué pendant que la coroutine attend la fin du délai, comme illustré dans le schéma suivant :
La coroutine suspend l'exécution après l'appel de la fonction delay()
avec la valeur d'intervalle souhaitée, mais ne la bloque pas. Une fois le délai écoulé, la coroutine reprend l'exécution et met à jour la valeur de la propriété currentProgress
.
4. Démarrer la course
Lorsque l'utilisateur appuie sur le bouton Start (Démarrer), vous devez démarrer la course en appelant la fonction de suspension run()
pour chacune des deux instances de joueurs. Pour ce faire, vous devez lancer une coroutine pour appeler la fonction run()
.
Lorsque vous lancez une coroutine pour démarrer la course, vous devez vous assurer que les conditions suivantes s'appliquent :
- Les deux coureurs commencent à courir dès que l'utilisateur clique sur le bouton Start (Démarrer), c'est-à-dire lorsque les coroutines se lancent.
- Les deux coureurs marquent une pause ou s'arrêtent lorsque l'utilisateur clique sur le bouton Pause (Mettre en pause) ou Reset (Réinitialiser), c'est-à-dire lorsque les coroutines sont annulées.
- Lorsque l'utilisateur ferme l'application, l'annulation est gérée correctement, c'est-à-dire que toutes les coroutines sont annulées et liées à un cycle de vie.
Dans le premier atelier de programmation, vous avez découvert que vous ne pouvez appeler une fonction de suspension qu'à partir d'une autre fonction de suspension. Pour appeler des fonctions de suspension de façon sécurisée depuis un composable, vous devez utiliser le composable LaunchedEffect()
. LaunchedEffect()
exécute la fonction de suspension fournie tant qu'elle reste dans la composition. Vous pouvez utiliser la fonction modulable LaunchedEffect()
pour effectuer toutes les opérations ci-dessous :
- Le composable
LaunchedEffect()
vous permet d'appeler de façon sécurisée des fonctions de suspension à partir de composables. - Lorsque la fonction
LaunchedEffect()
entre dans la composition, elle lance une coroutine avec le bloc de code transmis en tant que paramètre. Elle exécute la fonction de suspension fournie tant qu'elle reste dans la composition. Lorsqu'un utilisateur clique sur le bouton Start (Démarrer) de l'application Race Tracker, la fonctionLaunchedEffect()
entre dans la composition et lance une coroutine pour mettre à jour la progression. - La coroutine est annulée lorsque
LaunchedEffect()
quitte la composition. Dans l'application, si l'utilisateur clique sur le bouton Reset (Réinitialiser) ou Pause (Mettre en pause), la fonctionLaunchedEffect()
est supprimée de la composition et les coroutines sous-jacentes sont annulées.
Avec l'application Race Tracker, vous n'avez pas besoin de spécifier un coordinateur, car LaunchedEffect()
s'en charge.
Pour commencer la course, appelez la fonction run()
pour chaque coureur et procédez comme suit :
- Ouvrez le fichier
RaceTrackerApp.kt
situé dans le packagecom.example.racetracker.ui
. - Accédez au composable
RaceTrackerApp()
et ajoutez un appel au composableLaunchedEffect()
sur la ligne après la définition deraceInProgress
.
@Composable
fun RaceTrackerApp() {
...
var raceInProgress by remember { mutableStateOf(false) }
LaunchedEffect {
}
RaceTrackerScreen(...)
}
- Si les instances de
playerOne
ouplayerTwo
sont remplacées par des instances différentes,LaunchedEffect()
doit annuler et relancer les coroutines sous-jacentes. Pour vous en assurer, ajoutez les objetsplayerOne
etplayerTwo
en tant quekey
deLaunchedEffect
. De la même manière qu'un composableText()
est recomposé lorsque sa valeur de texte change, si l'un des arguments clés deLaunchedEffect()
change, la coroutine sous-jacente est annulée et relancée.
LaunchedEffect(playerOne, playerTwo) {
}
- Ajoutez un appel aux fonctions
playerOne.run()
etplayerTwo.run()
.
@Composable
fun RaceTrackerApp() {
...
var raceInProgress by remember { mutableStateOf(false) }
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
}
RaceTrackerScreen(...)
}
- Encapsulez le bloc
LaunchedEffect()
avec une conditionif
. La valeur initiale de cet état est définie sur "false
". La valeur de l'étatraceInProgress
est définie sur "true
" lorsque l'utilisateur clique sur le bouton Start (Démarrer) et que la commandeLaunchedEffect()
s'exécute.
if (raceInProgress) {
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
}
}
- Définissez l'indicateur
raceInProgress
sur "false
" pour terminer la course. Cette valeur est également définie sur "false
" lorsque l'utilisateur clique sur Pause (Mettre en pause). Lorsque cette valeur est définie sur "false
",LaunchedEffect()
assure que toutes les coroutines lancées sont annulées.
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
raceInProgress = false
}
- Exécutez l'application, puis cliquez sur Start (Démarrer). Le joueur 1 devrait terminer la course avant que le joueur 2 ne commence à courir, comme le montre la vidéo suivante :
Cela n'a pas l'air très équitable ! Dans la section suivante, vous lancerez des tâches simultanées afin que les deux joueurs puissent courir en même temps, vous vous familiariserez avec les concepts associés et implémenterez ce comportement.
5. Simultanéité structurée
La façon d'écrire du code à l'aide de coroutines est appelée simultanéité structurée. Ce style de programmation améliore la lisibilité de votre code et le temps de développement. L'idée de la simultanéité structurée est que les coroutines ont une hiérarchie : les tâches peuvent lancer des tâches secondaires, qui peuvent à leur tour lancer des sous-tâches. L'unité de cette hiérarchie est appelée champ d'application de coroutine. Les champs d'application de coroutine doivent toujours être associés à un cycle de vie.
Les API de coroutines sont conçues pour respecter cette simultanéité structurée. Vous ne pouvez appeler une fonction de suspension qu'à partir d'une fonction qui est marquée comme fonction de suspension. Cette limitation assure que vous appelez les fonctions de suspension à partir de constructeurs de coroutines, tels que launch
. Ces constructeurs sont eux-mêmes associés à un CoroutineScope
.
6. Lancer des tâches simultanées
- Pour permettre aux deux joueurs de courir simultanément, vous devez lancer deux coroutines distinctes et déplacer chaque appel à la fonction
run()
dans ces coroutines. Encapsulez l'appel à la fonctionplayerOne.run()
avec le constructeurlaunch
.
LaunchedEffect(playerOne, playerTwo) {
launch { playerOne.run() }
playerTwo.run()
raceInProgress = false
}
- De même, encapsulez l'appel à la fonction
playerTwo.run()
avec le constructeurlaunch
. Avec cette modification, l'application lance deux coroutines qui s'exécutent simultanément. Les deux joueurs peuvent désormais courir en même temps.
LaunchedEffect(playerOne, playerTwo) {
launch { playerOne.run() }
launch { playerTwo.run() }
raceInProgress = false
}
- Exécutez l'application, puis cliquez sur Start (Démarrer). Alors que vous vous attendez à ce que la course commence, le texte du bouton revient immédiatement à Start (Démarrer) de manière inattendue.
Lorsque les deux joueurs terminent la course, l'application Race Tracker doit réinitialiser le texte du bouton Pause (Mettre en pause) et le définir sur Start (Démarrer). Toutefois, l'application met à jour raceInProgress
immédiatement après le lancement des coroutines sans attendre la fin de la course :
LaunchedEffect(playerOne, playerTwo) {
launch {playerOne.run() }
launch {playerTwo.run() }
raceInProgress = false // This will update the state immediately, without waiting for players to finish run() execution.
}
L'indicateur raceInProgress
est mis à jour immédiatement pour les raisons suivantes :
- La fonction de constructeur
launch
lance une coroutine pour exécuterplayerOne.run()
et est immédiatement renvoyée pour exécuter la ligne suivante dans le bloc de code. - Le même flux d'exécution se produit avec la deuxième fonction de constructeur
launch
qui exécute la fonctionplayerTwo.run()
. - Dès que le deuxième constructeur
launch
est renvoyé, l'indicateurraceInProgress
est mis à jour. Le texte du bouton est alors immédiatement remplacé par Start (Démarrer) et la course ne commence pas.
Champ d'application de coroutine
La fonction de suspension coroutineScope
crée un CoroutineScope
et appelle le bloc de suspension spécifié avec le champ d'application actuel. Le champ d'application hérite son coroutineContext
du champ d'application de LaunchedEffect()
.
Le champ d'application est renvoyé dès que le bloc donné et toutes ses coroutines enfants sont terminés. Pour l'application RaceTracker
, il est renvoyé une fois que les deux objets des joueurs ont terminé d'exécuter la fonction run()
.
- Pour faire en sorte que la fonction
run()
deplayerOne
etplayerTwo
termine l'exécution avant de mettre à jour l'indicateurraceInProgress
, encapsulez les deux constructeurs launch avec un bloccoroutineScope
.
LaunchedEffect(playerOne, playerTwo) {
coroutineScope {
launch { playerOne.run() }
launch { playerTwo.run() }
}
raceInProgress = false
}
- Exécutez l'application dans un émulateur ou sur un appareil Android. Vous devriez voir l'écran suivant :
- Cliquez sur le bouton Start (Démarrer). Le joueur 2 est plus rapide que le joueur 1. Une fois la course terminée, c'est-à-dire lorsque les deux joueurs atteignent 100 % de progression, le texte du bouton Pause (Mettre en pause) devient Start (Démarrer). Vous pouvez cliquer sur le bouton Reset (Réinitialiser) pour réinitialiser la course et réexécuter la simulation. La course est présentée dans la vidéo suivante :
Le flux d'exécution est illustré dans le schéma suivant :
- Lorsque le bloc
LaunchedEffect()
s'exécute, le contrôle est transféré au bloccoroutineScope{..}
. - Le bloc
coroutineScope
lance les deux coroutines simultanément et attend la fin de leur exécution. - Une fois l'exécution terminée, l'indicateur
raceInProgress
est mis à jour.
Le bloc coroutineScope
ne renvoie de résultat et ne se poursuit qu'une fois que le code à l'intérieur du bloc a terminé son exécution. Pour le code situé en dehors du bloc, la présence ou l'absence de simultanéité ne devient qu'un simple détail d'implémentation. Ce style de programmation, appelé simultanéité structurée, offre une approche structurée de la programmation simultanée.
Lorsque vous cliquez sur le bouton Reset (Réinitialiser), une fois la course terminée, les coroutines sont annulées et la progression des deux joueurs est réinitialisée sur 0
.
Pour voir comment les coroutines sont annulées lorsque l'utilisateur clique sur le bouton Reset (Réinitialiser), procédez comme suit :
- Encapsulez le corps de la méthode
run()
dans un bloc try-catch, comme indiqué dans l'extrait de code suivant :
suspend fun run() {
try {
while (currentProgress < maxProgress) {
delay(progressDelayMillis)
currentProgress += progressIncrement
}
} catch (e: CancellationException) {
Log.e("RaceParticipant", "$name: ${e.message}")
throw e // Always re-throw CancellationException.
}
}
- Exécutez l'application et cliquez sur le bouton Start (Démarrer).
- Après quelques incréments de progression, cliquez sur le bouton Réinitialiser.
- Vérifiez que le message suivant s'affiche dans Logcat :
Player 1: StandaloneCoroutine was cancelled Player 2: StandaloneCoroutine was cancelled
7. Écrire des tests unitaires pour tester les coroutines
Le code de tests unitaires utilisant des coroutines nécessite une attention particulière, car leur exécution peut être asynchrone et se produire sur plusieurs threads.
Pour appeler des fonctions de suspension dans les tests, vous devez vous trouver dans une coroutine. Étant donné que les fonctions de test JUnit ne sont pas elles-mêmes des fonctions de suspension, vous devez utiliser le constructeur de coroutine runTest
. Ce constructeur fait partie de la bibliothèque kotlinx-coroutines-test
et est conçu pour exécuter des tests. Il exécute le texte test dans une nouvelle coroutine.
Comme runTest
fait partie de la bibliothèque kotlinx-coroutines-test
, vous devez ajouter sa dépendance.
Pour ce faire, procédez comme suit :
- Ouvrez le fichier
build.gradle.kts
du module, situé dans le répertoireapp
du volet Project (Projet).
- Faites défiler le fichier vers le bas jusqu'au bloc
dependencies{}
. - Ajoutez une dépendance à l'aide de la configuration
testImplementation
dans la bibliothèquekotlinx-coroutines-test
.
plugins {
...
}
android {
...
}
dependencies {
...
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
- Dans la barre de notification en haut du fichier build.gradle.kts, cliquez sur Sync Now (Synchroniser maintenant) pour terminer l'importation et la compilation, comme illustré dans la capture d'écran suivante :
Une fois la compilation terminée, vous pouvez commencer à écrire des tests.
Implémenter des tests unitaires pour commencer et terminer la course
Pour que la progression de la course se mette correctement à jour pendant les différentes phases de la course, vos tests unitaires doivent couvrir différents scénarios. Dans cet atelier de programmation, nous allons étudier deux scénarios :
- Progression après le début de la course.
- Progression une fois la course terminée.
Pour vérifier si la progression de la course se met à jour correctement après le début de la course, vous devez confirmer que la progression actuelle est définie sur 1 après la fin de la durée de raceParticipant.progressDelayMillis
.
Pour implémenter le scénario de test, procédez comme suit :
- Accédez au fichier
RaceParticipantTest.kt
situé sous l'ensemble de sources de test. - Pour définir le test, après la définition
raceParticipant
, créez une fonctionraceParticipant_RaceStarted_ProgressUpdated()
et marquez-la avec l'annotation@Test
. Comme le bloc de test doit être placé dans le constructeurrunTest
, utilisez la syntaxe d'expression pour renvoyer le blocrunTest()
en tant que résultat de test.
class RaceParticipantTest {
private val raceParticipant = RaceParticipant(
...
)
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
}
}
- Ajoutez une variable
expectedProgress
en lecture seule et définissez-la sur1
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
}
- Pour simuler le début d'une course, utilisez le constructeur
launch
pour lancer une nouvelle coroutine et appeler la fonctionraceParticipant.run()
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
}
La valeur de la propriété raceParticipant.progressDelayMillis
détermine le délai à l'issue duquel la progression de la course est mise à jour. Pour pouvoir tester la progression une fois le délai de progressDelayMillis
écoulé, ajoutez un certain délai à votre test.
- Utilisez la fonction d'assistance
advanceTimeBy()
pour avancer le temps de la valeur deraceParticipant.progressDelayMillis
. La fonctionadvanceTimeBy()
permet de réduire la durée d'exécution du test.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
}
- Comme
advanceTimeBy()
n'exécute pas la tâche planifiée à la durée donnée, vous devez appeler la fonctionrunCurrent()
. Cette fonction exécute toutes les tâches en attente à l'heure actuelle.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
runCurrent()
}
- Pour que la progression soit mise à jour, ajoutez un appel à la fonction
assertEquals()
afin de vérifier si la valeur de la propriétéraceParticipant.currentProgress
correspond à celle de la variableexpectedProgress
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
runCurrent()
assertEquals(expectedProgress, raceParticipant.currentProgress)
}
- Exécutez le test pour vérifier qu'il a réussi.
Pour vérifier si la progression se met à jour correctement une fois la course terminée, vous devez confirmer que la progression actuelle est définie sur 100
.
Pour implémenter le test, procédez comme suit :
- Après la fonction de test
raceParticipant_RaceStarted_ProgressUpdated()
, créez une fonctionraceParticipant_RaceFinished_ProgressUpdated()
et marquez-la avec l'annotation@Test
. La fonction doit renvoyer un résultat de test à partir du blocrunTest{}
.
class RaceParticipantTest {
...
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
...
}
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
}
}
- Utilisez le constructeur
launch
pour lancer une nouvelle coroutine et ajouter un appel à la fonctionraceParticipant.run()
qu'elle contient.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
}
- Pour simuler la fin de la course, utilisez la fonction
advanceTimeBy()
afin de faire avancer le temps du coordinateur deraceParticipant.maxProgress * raceParticipant.progressDelayMillis
:
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
}
- Ajoutez un appel à la fonction
runCurrent()
pour exécuter les tâches en attente.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
runCurrent()
}
- Pour vous assurer que la progression se met à jour correctement, ajoutez un appel à la fonction
assertEquals()
afin de vérifier que la valeur de la propriétéraceParticipant.currentProgress
est égale à100
.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
runCurrent()
assertEquals(100, raceParticipant.currentProgress)
}
- Exécutez le test pour vérifier qu'il a réussi.
Relevez ce défi !
Appliquez les stratégies de test décrites dans l'atelier de programmation Écrire des tests unitaires pour ViewModel. Ajoutez les tests pour découvrir les scénarios de réussite, d'erreur et de cas limite.
Comparez le test que vous écrivez à ceux disponibles dans le code de solution.
8. Télécharger le code de solution
Pour télécharger le code de cet atelier de programmation terminé, utilisez les commandes Git suivantes :
git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-race-tracker.git cd basic-android-kotlin-compose-training-race-tracker
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous souhaitez voir le code de solution, affichez-le sur GitHub.
9. Conclusion
Félicitations ! Vous savez maintenant utiliser des coroutines pour gérer la simultanéité. Les coroutines permettent de gérer les tâches de longue durée qui pourraient autrement bloquer le thread principal et entraîner le blocage de votre application. Vous avez également appris à écrire des tests unitaires pour tester les coroutines.
Voici quelques-uns des avantages des coroutines :
- Lisibilité : le code que vous écrivez avec des coroutines permet de comprendre clairement la séquence d'exécution des lignes de code.
- Intégration Jetpack : de nombreuses bibliothèques Jetpack, telles que Compose et ViewModel, incluent des extensions compatibles avec toutes les coroutines. Certaines bibliothèques fournissent également leur propre champ d'application de coroutine que vous pouvez utiliser pour la simultanéité structurée.
- Simultanéité structurée : les coroutines rendent le code simultané sûr et facile à implémenter, éliminent le code récurrent inutile et assurent que les coroutines lancées par l'application ne sont pas perdues et qu'elles ne continuent pas à épuiser des ressources.
Résumé
- Les coroutines vous permettent d'écrire du code de longue durée qui s'exécute simultanément sans apprendre un nouveau style de programmation. Les coroutines sont conçues pour une exécution séquentielle.
- Le mot clé
suspend
sert à marquer une fonction ou un type de fonction pour indiquer sa disponibilité à exécuter, suspendre et reprendre l'exécution d'un ensemble d'instructions dans le code. - Une fonction
suspend
ne peut être appelée qu'à partir d'une autre fonction de suspension. - Vous pouvez démarrer une nouvelle coroutine à l'aide de la fonction de constructeur
launch
ouasync
. - Le contexte de coroutine, les constructeurs de coroutines, la tâche, le champ d'application de coroutine et le coordinateur sont les principaux composants de l'implémentation des coroutines.
- Les coroutines utilisent les coordinateurs pour déterminer les threads à utiliser pour son exécution.
- La tâche joue un rôle important pour assurer la simultanéité structurée en gérant le cycle de vie des coroutines et en maintenant la relation parent-enfant.
- Un
CoroutineContext
définit le comportement d'une coroutine à l'aide d'une tâche et d'un coordinateur de coroutine. - Un
CoroutineScope
permet de contrôler la durée de vie des coroutines par le biais de sa tâche et applique l'annulation et d'autres règles à ses enfants et à leurs enfants de manière récursive. - Le lancement, l'exécution, l'annulation et l'échec sont quatre opérations courantes dans l'exécution d'une coroutine.
- Les coroutines suivent un principe de simultanéité structurée.