Android Kotlin Fundamentals 06.3: utiliser LiveData pour contrôler les états des boutons

Cet atelier de programmation fait partie du cours Android Kotlin Fundamentals. Vous tirerez le meilleur parti de ce cours si vous suivez les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont répertoriés sur la page de destination des ateliers de programmation Android Kotlin Fundamentals.

Présentation

Cet atelier de programmation récapitule comment utiliser ViewModel et les fragments pour implémenter la navigation. N'oubliez pas que l'objectif est de placer la logique when pour accéder à ViewModel, mais de définir les chemins d'accès dans les fragments et le fichier de navigation. Pour atteindre cet objectif, vous utilisez des modèles de vue, des fragments, LiveData et des observateurs.

L'atelier de programmation s'achève par une démonstration intelligente du suivi des états des boutons, avec un minimum de code. Ainsi, chaque bouton est activé et cliquable uniquement lorsque l'utilisateur s'appuie dessus.

Ce que vous devez déjà savoir

Vous devez être au fait:

  • Créer une interface utilisateur de base à l'aide d'une activité, de fragments et de vues
  • Parcourir des fragments et utiliser safeArgs pour transmettre des données entre fragments
  • Affichez des modèles, et découvrez les usines de modèles, les transformations et LiveData ainsi que leurs observateurs.
  • Créer une base de données Room, créer un objet d'accès aux données et définir des entités
  • Utiliser des coroutines pour des interactions avec une base de données et pour d'autres tâches de longue durée

Points abordés

  • Comment mettre à jour un enregistrement existant concernant la qualité du sommeil dans la base de données.
  • Utiliser LiveData pour suivre l'état des boutons
  • Afficher un snack-bar en réponse à un événement

Objectifs de l'atelier

  • Développez l'application TrackMySleepQuality pour obtenir une évaluation de qualité, ajoutez la note à la base de données et affichez le résultat.
  • Utilisez LiveData pour déclencher l'affichage d'un snack-bar.
  • Utilisez LiveData pour activer et désactiver les boutons.

Dans cet atelier de programmation, vous allez créer l'enregistrement de qualité pour le sommeil et l'interface utilisateur finalisée de l'application TrackMySleepQuality.

L'application dispose de deux écrans, représentés par des fragments, comme illustré dans la figure ci-dessous.

Le premier écran, affiché à gauche, comporte des boutons permettant de démarrer et d'arrêter le suivi. Toutes les données sur le sommeil de l'utilisateur s'affichent à l'écran. Le bouton Effacer supprime définitivement toutes les données collectées par l'utilisateur.

Le deuxième écran, à droite, permet de sélectionner un niveau de qualité du sommeil. Dans l'application, la note est indiquée sous forme numérique. À des fins de développement, l'application affiche les icônes des visages et leurs équivalents numériques.

Le parcours utilisateur est le suivant:

  • L'utilisateur ouvre l'application et voit l'écran de suivi du sommeil.
  • L'utilisateur appuie sur le bouton Démarrer. Cette action enregistre l'heure de début et l'affiche. Le bouton Start (Démarrer) est désactivé, tout comme le bouton Stop (Arrêter).
  • L'utilisateur appuie sur le bouton Arrêter. L'heure de fin est enregistrée, et l'écran de qualité du sommeil s'affiche.
  • L'utilisateur sélectionne une icône de qualité du sommeil. L'écran se ferme et l'écran de suivi affiche le temps et la qualité de votre sommeil. Le bouton Stop (Arrêter) est désactivé, tout comme le bouton Start (Démarrer). L'application est prête pour une autre nuit.
  • Le bouton Effacer est activé chaque fois que la base de données contient des données. Lorsque l'utilisateur appuie sur le bouton Clear (Effacer), toutes les données sont effacées sans recours. Aucun message ne s'affiche.

Cette application utilise une architecture simplifiée, comme indiqué ci-dessous dans le contexte de l'architecture complète. L'application n'utilise que les composants suivants:

  • contrôleur d'interface utilisateur
  • Afficher le modèle et LiveData
  • Une base de données Room

Cet atelier de programmation suppose que vous savez mettre en œuvre la navigation à l'aide de fragments et du fichier de navigation. Pour vous économiser, vous bénéficiez d'une grande partie de ce code.

Étape 1: Inspectez le code

  1. Pour commencer, poursuivez avec le code que vous avez obtenu à la fin du dernier atelier de programmation, ou téléchargez le code de démarrage.
  2. Dans votre code de démarrage, inspectez SleepQualityFragment. Cette classe gonfle la mise en page, obtient l'application et renvoie binding.root.
  3. Ouvrez le fichier navigation.xml dans l'éditeur de conception. Vous voyez qu'il existe un chemin de navigation de SleepTrackerFragment à SleepQualityFragment, et de nouveau de SleepQualityFragment à SleepTrackerFragment.



  4. Inspectez le code pour le fichier navigation.xml. Recherchez en particulier le <argument> nommé sleepNightKey.

    Lorsque l'utilisateur passe de SleepTrackerFragment à SleepQualityFragment,, l'application transmet un sleepNightKey à SleepQualityFragment pour la nuit qui doit être mise à jour.

Étape 2: Ajoutez une fonctionnalité de navigation pour le suivi de la qualité du sommeil

Le graphique de navigation inclut déjà les chemins de la SleepTrackerFragment à la SleepQualityFragment, puis inversement. Toutefois, les gestionnaires de clics qui implémentent la navigation d'un fragment à l'autre ne sont pas encore codés. Vous ajoutez ce code dans la ViewModel.

Dans le gestionnaire de clics, vous définissez un LiveData qui change lorsque vous souhaitez que l'application accède à une autre destination. Le fragment observe ce LiveData. Lorsque les données changent, le fragment accède à la destination et indique au modèle de vue que cette opération est terminée, ce qui réinitialise la variable d'état.

  1. Ouvrez SleepTrackerViewModel. Vous devez ajouter une barre de navigation pour que, lorsque l'utilisateur appuie sur le bouton Arrêter, l'application accède à SleepQualityFragment pour obtenir un niveau de qualité.
  2. Dans SleepTrackerViewModel, créez un LiveData qui change lorsque vous souhaitez que l'application accède à SleepQualityFragment. Utilisez une encapsulation pour n'exposer qu'une version observable de LiveData à ViewModel.

    Vous pouvez placer ce code au niveau supérieur du corps de la classe.
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()

val navigateToSleepQuality: LiveData<SleepNight>
   get() = _navigateToSleepQuality
  1. Ajoutez une fonction doneNavigating() qui réinitialise la variable qui déclenche la navigation.
fun doneNavigating() {
   _navigateToSleepQuality.value = null
}
  1. Dans le gestionnaire de clics du bouton Arrêter, onStopTracking(), déclenche la navigation vers SleepQualityFragment. Définissez la variable _navigateToSleepQuality à la fin de la fonction comme dernière chose du bloc launch{}. Notez que cette variable est définie sur night. Lorsque cette variable a une valeur, l'application accède à la valeur SleepQualityFragment et transmet la nuit.
_navigateToSleepQuality.value = oldNight
  1. Le SleepTrackerFragment doit observer _navigateToSleepQuality pour que l'application sache quand naviguer. Dans SleepTrackerFragment, ajoutez un observateur pour onCreateView() dans navigateToSleepQuality(). Notez que l'importation est ambiguë et que vous devez importer androidx.lifecycle.Observer.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})

  1. Dans le bloc observateur, parcourez et transmettez l'ID de la nuit, puis appelez doneNavigating(). Si votre importation est ambiguë, importez androidx.navigation.fragment.findNavController.
night ->
night?.let {
   this.findNavController().navigate(
           SleepTrackerFragmentDirections
                   .actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
   sleepTrackerViewModel.doneNavigating()
}
  1. Créez et exécutez votre application. Appuyez sur Démarrer, puis sur Arrêter pour accéder à l'écran SleepQualityFragment. Pour revenir en arrière, utilisez le bouton "Retour" du système.

Dans cette tâche, vous allez enregistrer la qualité du sommeil et revenir au fragment de l'outil de suivi du sommeil. L'affichage doit se mettre à jour automatiquement pour présenter la nouvelle valeur à l'utilisateur. Vous devez créer un ViewModel et une ViewModelFactory, puis mettre à jour le SleepQualityFragment.

Étape 1: Créer un ViewModel et un ViewModelFactory

  1. Dans le package sleepquality, créez ou ouvrez SleepQualityViewModel.kt.
  2. Créez une classe SleepQualityViewModel qui utilise un sleepNightKey et une base de données comme arguments. Tout comme pour le SleepTrackerViewModel, vous devez transmettre le database depuis l'usine. Vous devez également transmettre les sleepNightKey depuis le menu de navigation.
class SleepQualityViewModel(
       private val sleepNightKey: Long = 0L,
       val database: SleepDatabaseDao) : ViewModel() {
}
  1. Dans la classe SleepQualityViewModel, définissez une valeur Job et uiScope, puis remplacez onCleared().
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Pour revenir à la SleepTrackerFragment à l'aide du même schéma que celui ci-dessus, déclarez _navigateToSleepTracker. Implémentez navigateToSleepTracker et doneNavigating().
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()

val navigateToSleepTracker: LiveData<Boolean?>
   get() = _navigateToSleepTracker

fun doneNavigating() {
   _navigateToSleepTracker.value = null
}
  1. Créez un gestionnaire de clics, onSetSleepQuality(), pour toutes les images de qualité du sommeil à utiliser.

    Utilisez le même modèle de coroutine que dans l'atelier de programmation précédent:
  • Lancez une coroutine dans uiScope et passez au coordinateur d'E/S.
  • Récupérez tonight à l'aide de sleepNightKey.
  • Définissez la qualité du sommeil.
  • Mettez à jour la base de données.
  • Déclenchez la navigation.

Notez que l'exemple de code ci-dessous fonctionne avec le gestionnaire de clics au lieu d'exclure l'opération de base de données dans le contexte.

fun onSetSleepQuality(quality: Int) {
        uiScope.launch {
            // IO is a thread pool for running operations that access the disk, such as
            // our Room database.
            withContext(Dispatchers.IO) {
                val tonight = database.get(sleepNightKey) ?: return@withContext
                tonight.sleepQuality = quality
                database.update(tonight)
            }

            // Setting this state variable to true will alert the observer and trigger navigation.
            _navigateToSleepTracker.value = true
        }
    }
  1. Dans le package sleepquality, créez ou ouvrez SleepQualityViewModelFactory.kt, puis ajoutez la classe SleepQualityViewModelFactory, comme indiqué ci-dessous. Cette classe utilise une version identique au code récurrent que vous avez vu précédemment. Examinez le code avant de continuer.
class SleepQualityViewModelFactory(
       private val sleepNightKey: Long,
       private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
           return SleepQualityViewModel(sleepNightKey, dataSource) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Étape 2: Mettez à jour le paramètre SleepQualityFragment

  1. Ouvrez SleepQualityFragment.kt.
  2. Dans onCreateView(), après avoir obtenu le application, vous devez obtenir le arguments fourni avec la navigation. Cet argument figure dans SleepQualityFragmentArgs. Vous devez les extraire du groupe.
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
  1. Ensuite, obtenez dataSource.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. Créez une fabrique en transmettant le dataSource et le sleepNightKey.
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
  1. Obtenez une référence ViewModel.
val sleepQualityViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepQualityViewModel::class.java)
  1. Ajoutez le ViewModel à l'objet de liaison. Si une erreur se produit au niveau de l'objet de liaison, ignorez-la pour l'instant.
binding.sleepQualityViewModel = sleepQualityViewModel
  1. Ajoutez l'observateur. Lorsque vous y êtes invité, importez androidx.lifecycle.Observer.
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
   if (it == true) { // Observed state is true.
       this.findNavController().navigate(
               SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
       sleepQualityViewModel.doneNavigating()
   }
})

Étape 3: Mettez à jour le fichier de mise en page et exécutez l'application

  1. Ouvrez le fichier de mise en page fragment_sleep_quality.xml. Dans le bloc <data>, ajoutez une variable pour SleepQualityViewModel.
 <data>
       <variable
           name="sleepQualityViewModel"
           type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
   </data>
  1. Pour chacune des six images de qualité du sommeil, ajoutez un gestionnaire de clics comme celui présenté ci-dessous. Associez le niveau de qualité à l'image.
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
  1. Nettoyez et recréez votre projet. Cela devrait résoudre toutes les erreurs liées à l'objet de liaison. Sinon, videz le cache (File > Invalidate Caches / Restart) (Modifier les valeurs et invalider les caches/Redémarrer) et recréez l'application.

Félicitations ! Vous venez de créer une application de base de données Room complète à l'aide de coroutines.

Votre application fonctionne désormais très bien. L'utilisateur peut appuyer sur Démarrer et Arrêter autant de fois qu'il le souhaite. Lorsque l'utilisateur appuie sur Arrêter, il peut saisir une qualité de sommeil. Lorsque l'utilisateur appuie sur Effacer, toutes les données sont supprimées silencieusement en arrière-plan. Toutefois, tous les boutons sont toujours activés et cliquables, ce qui n'empêche pas l'application de fonctionner. Toutefois, les utilisateurs peuvent créer des nuits de sommeil incomplètes.

Dans cette dernière tâche, vous allez apprendre à utiliser des cartes de transformation pour gérer la visibilité des boutons afin que les utilisateurs puissent uniquement faire le bon choix. Toutefois, une fois la suppression effectuée, vous pouvez utiliser une méthode similaire pour afficher un message d'accueil.

Étape 1: Mettre à jour les états du bouton

L'idée est de définir l'état du bouton de sorte qu'au départ, seul le bouton Start (Démarrer) soit activé, ce qui signifie qu'il est cliquable.

Une fois que l'utilisateur a appuyé sur Démarrer, le bouton Arrêter devient activé, mais pas Démarrer. Le bouton Effacer n'est activé que lorsqu'il contient des données.

  1. Ouvrez le fichier de mise en page fragment_sleep_tracker.xml.
  2. Ajoutez la propriété android:enabled à chaque bouton. La propriété android:enabled est une valeur booléenne qui indique si le bouton est activé ou non. (Vous pouvez appuyer sur un bouton activé, mais pas sur un bouton désactivé.) Attribuez à la propriété la valeur d'une variable d'état que vous définirez immédiatement.

start_button :

android:enabled="@{sleepTrackerViewModel.startButtonVisible}"

stop_button :

android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"

clear_button :

android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
  1. Ouvrez SleepTrackerViewModel et créez trois variables correspondantes. Attribuez à chaque variable une transformation qui la teste.
  • Le bouton Démarrer doit être activé lorsque tonight est défini sur null.
  • Le bouton Arrêter doit être activé lorsque la valeur de tonight est différente de null.
  • Le bouton Effacer ne doit être activé que si nights, et donc la base de données, contient des nuits de sommeil.
val startButtonVisible = Transformations.map(tonight) {
   it == null
}
val stopButtonVisible = Transformations.map(tonight) {
   it != null
}
val clearButtonVisible = Transformations.map(nights) {
   it?.isNotEmpty()
}
  1. Exécutez votre application et testez les boutons.

Étape 2: Utilisez un snack-bar pour informer l'utilisateur

Une fois que l'utilisateur a effacé la base de données, affichez une confirmation à l'aide du widget Snackbar. Un snackbar fournit de brèves informations sur une opération par le biais d'un message en bas de l'écran. Un snack-bar disparaît après un délai avant expiration, après une interaction de l'utilisateur ailleurs sur l'écran ou après que l'utilisateur a fait glisser le snack-bar hors de l'écran.

Afficher le snack-bar est une tâche d'interface utilisateur qui doit se produire dans le fragment. La barre de notification s'affiche dans la ViewModel. Pour configurer et déclencher un snack-bar lorsque les données sont effacées, vous pouvez utiliser la même technique que pour déclencher la navigation.

  1. Dans SleepTrackerViewModel, créez l'événement encapsulé.
private var _showSnackbarEvent = MutableLiveData<Boolean>()

val showSnackBarEvent: LiveData<Boolean>
   get() = _showSnackbarEvent
  1. Intégrez ensuite doneShowingSnackbar().
fun doneShowingSnackbar() {
   _showSnackbarEvent.value = false
}
  1. Dans SleepTrackerFragment, ajoutez un observateur dans onCreateView() :
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
  1. Dans le bloc observateur, affichez le snack-bar et réinitialisez immédiatement l'événement.
   if (it == true) { // Observed state is true.
       Snackbar.make(
               activity!!.findViewById(android.R.id.content),
               getString(R.string.cleared_message),
               Snackbar.LENGTH_SHORT // How long to display the message.
       ).show()
       sleepTrackerViewModel.doneShowingSnackbar()
   }
  1. Dans SleepTrackerViewModel, déclenchez l'événement dans la méthode onClear(). Pour ce faire, définissez la valeur de l'événement sur true dans le bloc launch:
_showSnackbarEvent.value = true
  1. Créez et exécutez votre application.

Projet Android Studio : TrackMySleepQualityFinal

La mise en œuvre du suivi de la qualité du sommeil dans cette appli est comme lire de la musique que vous connaissez bien dans une nouvelle clé. Bien que les détails changent, le modèle sous-jacent de ce que vous avez fait dans les ateliers de programmation précédents dans cette leçon reste le même. La connaissance de ces tendances accélère le codage, car il permet de réutiliser le code d'applications existantes. Voici quelques-uns des schémas utilisés dans ce cours:

  • Créez un ViewModel et un ViewModelFactory, puis configurez une source de données.
  • Déclenchez la navigation. Pour séparer les problèmes, placez le gestionnaire de clics dans le modèle d'affichage et le menu de navigation dans le fragment.
  • Utilisez l'encapsulation avec LiveData pour suivre les changements d'état et y répondre.
  • Utilisez des transformations avec LiveData.
  • Créez une base de données singleton.
  • Définissez des coroutines pour les opérations de base de données.

Déclenchement de la navigation

Vous pouvez définir les chemins de navigation possibles entre les fragments dans un fichier de navigation. Il existe plusieurs manières de déclencher la navigation d'un fragment à l'autre. Exemples :

  • Définissez des gestionnaires onClick pour déclencher la navigation vers un fragment de destination.
  • Pour activer la navigation d'un fragment à l'autre, procédez comme suit:
  • Définissez une valeur LiveData à enregistrer si la navigation doit se produire.
  • Associer un observateur à cette valeur LiveData.
  • Votre code modifie ensuite cette valeur chaque fois que la navigation doit être déclenchée ou terminée.

Définir l'attribut android:enabled

  • L'attribut android:enabled est défini dans TextView et hérité de toutes les sous-classes, y compris Button.
  • L'attribut android:enabled détermine si un paramètre View est activé. La signification de "Activé" varie en fonction des sous-classes. Par exemple, un élément EditText non activé empêche l'utilisateur de modifier le texte qu'il contient, et un élément Button désactivé empêche l'utilisateur d'appuyer sur le bouton.
  • L'attribut enabled est différent de l'attribut visibility.
  • Vous pouvez utiliser des cartes de transformation pour définir la valeur de l'attribut des boutons enabled en fonction de l'état d'un autre objet ou d'une autre variable.

Autres points abordés dans cet atelier de programmation:

  • Pour déclencher des notifications, vous pouvez utiliser la même technique que pour déclencher la navigation.
  • Vous pouvez utiliser un Snackbar pour avertir l'utilisateur.

Cours Udacity:

Documentation pour les développeurs Android:

Cette section répertorie les devoirs possibles pour les élèves qui effectuent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. C'est à l'enseignant de procéder comme suit:

  • Si nécessaire, rendez-les.
  • Communiquez aux élèves comment rendre leurs devoirs.
  • Notez les devoirs.

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ils n'ont pas besoin d'attribuer les devoirs de leur choix.

Si vous suivez vous-même cet atelier de programmation, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.

Répondez à ces questions.

Question 1

Pour que votre application puisse déclencher la navigation d'un fragment à l'autre, vous pouvez utiliser une valeur LiveData afin d'indiquer si la navigation doit être déclenchée ou non.

Quelles sont les étapes d'utilisation d'une valeur LiveData, appelée gotoBlueFragment, pour déclencher la navigation entre le fragment rouge et le fragment bleu ? Sélectionnez toutes les réponses applicables :

  • Dans ViewModel, définissez la valeur LiveData sur gotoBlueFragment.
  • Dans RedFragment, observez la valeur gotoBlueFragment. Implémentez le code observe{} pour accéder à BlueFragment le cas échéant, puis réinitialisez la valeur de gotoBlueFragment pour indiquer que la navigation est terminée.
  • Assurez-vous que votre code définit la variable gotoBlueFragment sur la valeur qui déclenche la navigation à chaque fois que l'application doit passer de RedFragment à BlueFragment.
  • Assurez-vous que votre code définit un gestionnaire onClick pour l'élément View sur lequel l'utilisateur clique pour accéder à BlueFragment, où le gestionnaire onClick observe la valeur goToBlueFragment.

Question 2

Vous pouvez vérifier si un Button est activé (cliquable) ou non en utilisant LiveData. Comment vous assurez-vous que votre application modifie le bouton UpdateNumber pour que:

  • Le bouton est activé si la valeur de myNumber est supérieure à 5.
  • Le bouton n'est pas activé si myNumber est égal ou inférieur à 5.

Supposons que la mise en page contenant le bouton UpdateNumber inclut la variable <data> pour NumbersViewModel, comme illustré ci-dessous:

<data>
   <variable
       name="NumbersViewModel"
       type="com.example.android.numbersapp.NumbersViewModel" />
</data>

Supposons que l'ID du bouton figure dans le fichier de mise en page:

android:id="@+id/update_number_button"

Que devez-vous faire d'autre ? Sélectionnez toutes les réponses applicables.

  • Dans la classe NumbersViewModel, définissez une variable LiveData, myNumber, qui représente le nombre. Définissez également une variable dont la valeur est définie en appelant Transform.map() sur la variable myNumber, qui renvoie une valeur booléenne indiquant si le nombre est supérieur ou égal à 5.

    Plus précisément, dans le fichier ViewModel, ajoutez le code suivant:
val myNumber: LiveData<Int>

val enableUpdateNumberButton = Transformations.map(myNumber) {
   myNumber > 5
}
  • Dans la mise en page XML, définissez l'attribut android:enabled de update_number_button button sur NumberViewModel.enableUpdateNumbersButton.
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"
  • Dans la Fragment qui utilise la classe NumbersViewModel, ajoutez un observateur à l'attribut enabled du bouton.

    Plus précisément, dans le fichier Fragment, ajoutez le code suivant:
// Observer for the enabled attribute
viewModel.enabled.observe(this, Observer<Boolean> { isEnabled ->
   myNumber > 5
})
  • Dans le fichier de mise en page, définissez l'attribut android:enabled de update_number_button button sur "Observable".

Démarrez la leçon suivante : 7.1 Principes de base de RecyclerView.

Pour obtenir des liens vers d'autres ateliers de programmation dans ce cours, consultez la page de destination des ateliers de programmation Android Kotlin Fundamentals.