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
safeArgspour 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
LiveDatapour 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
LiveDatapour déclencher l'affichage d'un snack-bar. - Utilisez
LiveDatapour 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
- Pour commencer, continuez avec votre propre code à la fin du dernier atelier de programmation ou téléchargez le code de démarrage.
- Dans votre code de démarrage, inspectez
SleepQualityFragment. Cette classe augmente la mise en page, obtient l'application et renvoiebinding.root. - Ouvrez navigation.xml dans l'éditeur de conception. Vous voyez qu'il existe un chemin de navigation de
SleepTrackerFragmentàSleepQualityFragment, et inversement deSleepQualityFragmentàSleepTrackerFragment.
- Inspectez le code de navigation.xml. Recherchez en particulier le
<argument>nommésleepNightKey.
Lorsque l'utilisateur passe deSleepTrackerFragmentàSleepQualityFragment,, l'application transmet unsleepNightKeyàSleepQualityFragmentpour 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.
- Ouvrez
SleepTrackerViewModel. Vous devez ajouter une navigation pour que, lorsque l'utilisateur appuie sur le bouton Arrêter, l'application accède àSleepQualityFragmentpour collecter une note de qualité. - Dans
SleepTrackerViewModel, créez unLiveDataqui change lorsque vous souhaitez que l'application accède àSleepQualityFragment. Utilisez l'encapsulation pour n'exposer qu'une version récupérable deLiveDataà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- Ajoutez une fonction
doneNavigating()qui réinitialise la variable qui déclenche la navigation.
fun doneNavigating() {
_navigateToSleepQuality.value = null
}- Dans le gestionnaire de clics du bouton Stop,
onStopTracking(), déclenchez la navigation versSleepQualityFragment. Définissez la variable _navigateToSleepQualityà la fin de la fonction, en dernier dans le bloclaunch{}. Notez que cette variable est définie surnight. Lorsque cette variable a une valeur, l'application accède àSleepQualityFragment, en transmettant la nuit.
_navigateToSleepQuality.value = oldNightSleepTrackerFragmentdoit observer _navigateToSleepQualitypour que l'application sache quand naviguer. DansSleepTrackerFragment, dansonCreateView(), ajoutez un observateur pournavigateToSleepQuality(). Notez que l'importation pour cela est ambiguë et que vous devez importerandroidx.lifecycle.Observer.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
- 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ë, importezandroidx.navigation.fragment.findNavController.
night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}- 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
- Dans le package
sleepquality, créez ou ouvrez SleepQualityViewModel.kt. - Créez une classe
SleepQualityViewModelqui accepte unsleepNightKeyet une base de données comme arguments. Comme pourSleepTrackerViewModel, vous devez transmettredatabasedepuis la fabrique. Vous devez également transmettre lesleepNightKeyde la navigation.
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}- Dans la classe
SleepQualityViewModel, définissezJobetuiScope, puis remplacezonCleared().
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}- Pour revenir à
SleepTrackerFragmenten utilisant le même modèle que ci-dessus, déclarez_navigateToSleepTracker. ImplémenteznavigateToSleepTrackeretdoneNavigating().
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}- 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
uiScopeet passez au répartiteur d'E/S. - Obtenez
tonightà l'aide desleepNightKey. - 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
}
}- Dans le package
sleepquality, créez ou ouvrezSleepQualityViewModelFactory.kt, puis ajoutez la classeSleepQualityViewModelFactory, 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
- Ouvrez
SleepQualityFragment.kt. - Dans
onCreateView(), après avoir obtenu leapplication, vous devez obtenir leargumentsfourni avec la navigation. Ces arguments se trouvent dansSleepQualityFragmentArgs. Vous devez les extraire du bundle.
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)- Ensuite, récupérez le
dataSource.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao- Créez une fabrique en transmettant
dataSourceetsleepNightKey.
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)- Obtenez une référence
ViewModel.
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)- Ajoutez
ViewModelà l'objet de liaison. (Si une erreur s'affiche avec l'objet de liaison, ignorez-la pour le moment.)
binding.sleepQualityViewModel = sleepQualityViewModel- 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
- Ouvrez le fichier de mise en page
fragment_sleep_quality.xml. Dans le bloc<data>, ajoutez une variable pourSleepQualityViewModel.
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>- 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)}"- 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.
- Ouvrez le fichier de mise en page
fragment_sleep_tracker.xml. - Ajoutez la propriété
android:enabledà chaque bouton. La propriétéandroid:enabledest 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}"- Ouvrez
SleepTrackerViewModelet créez trois variables correspondantes. Attribuez à chaque variable une transformation qui la teste.
- Le bouton Start (Démarrer) doit être activé lorsque
tonightestnull. - Le bouton Arrêter doit être activé lorsque
tonightn'est pasnull. - 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()
}- 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.
- Dans le
SleepTrackerViewModel, créez l'événement encapsulé.
private var _showSnackbarEvent = MutableLiveData<Boolean>()
val showSnackBarEvent: LiveData<Boolean>
get() = _showSnackbarEvent- Implémentez ensuite
doneShowingSnackbar().
fun doneShowingSnackbar() {
_showSnackbarEvent.value = false
}- Dans
SleepTrackerFragment, dansonCreateView(), ajoutez un observateur :
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })- 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()
}- Dans
SleepTrackerViewModel, déclenchez l'événement dans la méthodeonClear(). Pour ce faire, définissez la valeur de l'événement surtruedans le bloclaunch:
_showSnackbarEvent.value = true- 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
ViewModelet unViewModelFactory, 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
LiveDatapour 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
onClickpour 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:enabledest défini dansTextViewet hérité par toutes les sous-classes, y comprisButton. - L'attribut
android:enableddétermine si unViewest activé ou non. La signification de "activé" varie selon la sous-classe. Par exemple, unEditTextnon activé empêche l'utilisateur de modifier le texte qu'il contient, et unButtonnon activé empêche l'utilisateur d'appuyer sur le bouton. - L'attribut
enabledest différent de l'attributvisibility. - Vous pouvez utiliser des cartes de transformation pour définir la valeur de l'attribut
enableddes 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
Snackbarpour 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 valeurLiveDatasurgotoBlueFragment. - Dans
RedFragment, observez la valeurgotoBlueFragment. Implémentez le codeobserve{}pour accéder àBlueFragmentle cas échéant, puis réinitialisez la valeur degotoBlueFragmentpour indiquer que la navigation est terminée. - Assurez-vous que votre code définit la variable
gotoBlueFragmentsur la valeur qui déclenche la navigation chaque fois que l'application doit passer deRedFragmentàBlueFragment. - Assurez-vous que votre code définit un gestionnaire
onClickpour leViewsur lequel l'utilisateur clique pour accéder àBlueFragment, où le gestionnaireonClickobserve la valeurgoToBlueFragment.
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
myNumbera une valeur supérieure à 5. - Le bouton n'est pas activé si
myNumberest 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 variableLiveData,myNumber, qui représente le nombre. Définissez également une variable dont la valeur est définie en appelantTransform.map()sur la variablemyNumber, qui renvoie une valeur booléenne indiquant si le nombre est supérieur à 5 ou non.
Plus précisément, dansViewModel, 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:enableddeupdate_number_button buttonsurNumberViewModel.enableUpdateNumbersButton.
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"- Dans le
Fragmentqui utilise la classeNumbersViewModel, ajoutez un observateur à l'attributenableddu bouton.
Plus précisément, dansFragment, 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:enableddeupdate_number_button buttonsur"Observable".
Passez à la leçon suivante :
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.