Principes de base d'Android en Kotlin 06.3 : Contrôler les états des boutons à l'aide de LiveData

Cet atelier de programmation fait partie du cours Principes de base d'Android en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.

Introduction

Cet atelier de programmation récapitule comment utiliser ViewModel et les fragments ensemble pour implémenter la navigation. N'oubliez pas que l'objectif est de placer la logique de when pour la navigation dans 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 ViewModels, des fragments, LiveData et des observateurs.

L'atelier de programmation se termine par une méthode astucieuse pour suivre l'état des boutons avec un minimum de code, afin que chaque bouton soit activé et cliquable uniquement lorsque l'utilisateur peut appuyer dessus.

Ce que vous devez déjà savoir

Vous devez maîtriser les éléments suivants :

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

Points abordés

  • Comment modifier un enregistrement de qualité du sommeil existant dans la base de données.
  • Comment utiliser LiveData pour suivre les états des boutons.
  • Comment afficher une snackbar en réponse à un événement.

Objectifs de l'atelier

  • Enrichissez l'application TrackMySleepQuality pour qu'elle puisse recevoir des notes d'évaluation, les ajouter à la base de données et afficher 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 la qualité du sommeil et l'UI finalisée de l'application TrackMySleepQuality.

L'application comporte 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. L'écran affiche toutes les données de sommeil de l'utilisateur. Le bouton Effacer supprime définitivement toutes les données que l'application a collectées pour l'utilisateur.

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

Voici le parcours de l'utilisateur :

  • L'utilisateur ouvre l'application et l'écran de suivi du sommeil s'affiche.
  • L'utilisateur appuie sur le bouton Démarrer. L'heure de début est enregistrée et affichée. Le bouton Start (Démarrer) est désactivé, et le bouton Stop (Arrêter) est activé.
  • L'utilisateur appuie sur le bouton Arrêter. L'heure de fin est enregistrée et l'écran de qualité du sommeil s'ouvre.
  • L'utilisateur sélectionne une icône de qualité du sommeil. L'écran se ferme et l'écran de suivi affiche l'heure de fin du sommeil et la qualité du sommeil. Le bouton Arrêter est désactivé et le bouton Démarrer est activé. L'application est prête pour une nouvelle 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 Effacer, toutes ses données sont effacées sans possibilité de retour en arrière. Aucun message de confirmation n'est affiché.

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 comment implémenter la navigation à l'aide de fragments et du fichier de navigation. Pour vous faire gagner du temps, une grande partie de ce code est fournie.

Étape 1 : Inspecter le code

  1. Pour commencer, continuez avec votre propre code à 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 augmente la mise en page, obtient l'application et renvoie binding.root.
  3. Ouvrez navigation.xml dans l'éditeur de conception. Vous voyez qu'il existe un chemin de navigation de SleepTrackerFragment à SleepQualityFragment, et inversement de SleepQualityFragment à SleepTrackerFragment.



  4. Inspectez le code de 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 la navigation pour le suivi de la qualité du sommeil

Le graphique de navigation inclut déjà les chemins d'accès de SleepTrackerFragment à SleepQualityFragment et inversement. Toutefois, les gestionnaires de clics qui implémentent la navigation d'un fragment à l'autre ne sont pas encore codés. Ajoutez ce code maintenant dans 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 qu'il a terminé, ce qui réinitialise la variable d'état.

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

    Vous pouvez placer ce code n'importe où 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 Stop, onStopTracking(), déclenchez la navigation vers SleepQualityFragment. Définissez la variable _navigateToSleepQuality à la fin de la fonction, en dernier dans le bloc launch{}. Notez que cette variable est définie sur night. Lorsque cette variable a une valeur, l'application accède à SleepQualityFragment, en transmettant la nuit.
_navigateToSleepQuality.value = oldNight
  1. SleepTrackerFragment doit observer _navigateToSleepQuality pour que l'application sache quand naviguer. Dans SleepTrackerFragment, dans onCreateView(), ajoutez un observateur pour navigateToSleepQuality(). Notez que l'importation pour cela est ambiguë et que vous devez importer androidx.lifecycle.Observer.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})

  1. Dans le bloc d'observateur, accédez à l'ID de la nuit en cours et transmettez-le, 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. Compilez et exécutez votre application. Appuyez sur Start (Démarrer), puis sur Stop (Arrêter), ce qui vous redirige vers 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 du suivi du sommeil. L'affichage doit se mettre à jour automatiquement pour afficher la nouvelle valeur à l'utilisateur. Vous devez créer un ViewModel et un ViewModelFactory, et mettre à jour le SleepQualityFragment.

Étape 1 : Créez un ViewModel et une ViewModelFactory

  1. Dans le package sleepquality, créez ou ouvrez SleepQualityViewModel.kt.
  2. Créez une classe SleepQualityViewModel qui accepte un sleepNightKey et une base de données comme arguments. Comme pour SleepTrackerViewModel, vous devez transmettre database depuis la fabrique. Vous devez également transmettre le sleepNightKey de la navigation.
class SleepQualityViewModel(
       private val sleepNightKey: Long = 0L,
       val database: SleepDatabaseDao) : ViewModel() {
}
  1. Dans la classe SleepQualityViewModel, définissez 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 à SleepTrackerFragment en utilisant le même modèle que 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 répartiteur d'E/S.
  • Obtenez 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 effectue tout le travail dans le gestionnaire de clics, au lieu de factoriser l'opération de base de données dans le contexte différent.

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 du même code récurrent que celui 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 SleepQualityFragment

  1. Ouvrez SleepQualityFragment.kt.
  2. Dans onCreateView(), après avoir obtenu le application, vous devez obtenir le arguments fourni avec la navigation. Ces arguments se trouvent dans SleepQualityFragmentArgs. Vous devez les extraire du bundle.
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
  1. Ensuite, récupérez le dataSource.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. Créez une fabrique en transmettant dataSource et 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 ViewModel à l'objet de liaison. (Si une erreur s'affiche avec l'objet de liaison, ignorez-la pour le moment.)
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 ci-dessous. Associez le niveau de qualité à l'image.
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
  1. Nettoyez et reconstruisez votre projet. Cela devrait résoudre les erreurs liées à l'objet de liaison. Sinon, videz le cache (File > Invalidate Caches / Restart) et reconstruisez votre 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 parfaitement. 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 la qualité de son sommeil. Lorsque l'utilisateur appuie sur Effacer, toutes les données sont effacées en arrière-plan, sans notification. Toutefois, tous les boutons sont toujours activés et cliquables, ce qui ne casse pas l'application, mais permet aux utilisateurs de créer des nuits de sommeil incomplètes.

Dans cette dernière tâche, vous allez apprendre à utiliser les cartes de transformation pour gérer la visibilité des boutons afin que les utilisateurs ne puissent faire que le bon choix. Vous pouvez utiliser une méthode similaire pour afficher un message convivial une fois que toutes les données ont été effacées.

Étape 1 : Mettez à jour les états des boutons

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

Une fois que l'utilisateur appuie sur Démarrer, le bouton Arrêter est activé, mais pas celui de Démarrer. Le bouton Effacer n'est activé que lorsque la base de données 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 dans un instant.

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 Start (Démarrer) doit être activé lorsque tonight est null.
  • Le bouton Arrêter doit être activé lorsque tonight n'est pas 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 une snackbar pour informer l'utilisateur

Une fois que l'utilisateur a effacé la base de données, affichez-lui une confirmation à l'aide du widget Snackbar. Une snackbar fournit un bref retour d'information sur une opération via un message en bas de l'écran. Une snackbar disparaît après un délai d'expiration, après une interaction de l'utilisateur ailleurs sur l'écran ou après que l'utilisateur l'a balayée hors de l'écran.

L'affichage de la snackbar est une tâche d'UI qui doit avoir lieu dans le fragment. La décision d'afficher le snackbar est prise dans ViewModel. Pour configurer et déclencher une snackbar lorsque les données sont effacées, vous pouvez utiliser la même technique que pour déclencher la navigation.

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

val showSnackBarEvent: LiveData<Boolean>
   get() = _showSnackbarEvent
  1. Implémentez ensuite doneShowingSnackbar().
fun doneShowingSnackbar() {
   _showSnackbarEvent.value = false
}
  1. Dans SleepTrackerFragment, dans onCreateView(), ajoutez un observateur :
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
  1. Dans le bloc d'observateur, affichez la snackbar 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

Implémenter le suivi de la qualité du sommeil dans cette application, c'est comme jouer un morceau de musique familier dans une nouvelle tonalité. 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 de cette leçon reste le même. En connaissant ces modèles, vous pouvez coder beaucoup plus rapidement, car vous pouvez réutiliser le code des applications existantes. Voici quelques-uns des modèles utilisés jusqu'à présent 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 préoccupations, placez le gestionnaire de clics dans le modèle de vue et la 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.
  • Configurez des coroutines pour les opérations de base de données.

Déclencher la navigation

Vous définissez les chemins de navigation possibles entre les fragments dans un fichier de navigation. Il existe différentes façons de déclencher la navigation d'un fragment à un autre. Exemples :

  • Définissez des gestionnaires onClick pour déclencher la navigation vers un fragment de destination.
  • Vous pouvez également activer la navigation d'un fragment à l'autre :
  • Définissez une valeur LiveData à enregistrer si la navigation doit avoir lieu.
  • Associez un observateur à cette valeur LiveData.
  • Votre code modifie ensuite cette valeur chaque fois que la navigation doit être déclenchée ou est terminée.

Définir l'attribut android:enabled

  • L'attribut android:enabled est défini dans TextView et hérité par toutes les sous-classes, y compris Button.
  • L'attribut android:enabled détermine si un View est activé ou non. La signification de "activé" varie selon la sous-classe. Par exemple, un EditText non activé empêche l'utilisateur de modifier le texte qu'il contient, et un Button non activé 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 enabled des boutons 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 à l'utilisateur, 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 suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :

  • Attribuer des devoirs si nécessaire
  • Indiquer aux élèves comment rendre leurs devoirs
  • Noter les devoirs

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.

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

Répondre aux questions suivantes

Question 1

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

Quelles sont les étapes à suivre pour utiliser une valeur LiveData, appelée gotoBlueFragment, afin de déclencher la navigation du fragment rouge vers le fragment bleu ? Sélectionnez toutes les réponses appropriées :

  • 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 le View sur lequel l'utilisateur clique pour accéder à BlueFragment, où le gestionnaire onClick observe la valeur goToBlueFragment.

Question2

Vous pouvez modifier l'état d'activation (cliquable ou non) d'un Button à l'aide de LiveData. Comment vous assureriez-vous que votre application modifie le bouton UpdateNumber de sorte que :

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

Supposons que la mise en page contenant le bouton UpdateNumber inclut la variable <data> pour NumbersViewModel, comme indiqué ici :

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

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

android:id="@+id/update_number_button"

Que devez-vous faire d'autre ? Plusieurs réponses possibles.

  • 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 à 5 ou non.

     Plus précisément, dans 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 le Fragment qui utilise la classe NumbersViewModel, ajoutez un observateur à l'attribut enabled du bouton.

     Plus précisément, dans 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".

Passez à la leçon suivante : 7.1 Principes de base de RecyclerView

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