1. Avant de commencer
Dans les ateliers précédents, vous avez découvert le cycle de vie des activités et les problèmes de cycle de vie liés aux modifications de configuration. Lorsqu'une configuration est modifiée, vous pouvez enregistrer les données d'une application de différentes manières, par exemple en utilisant rememberSaveable
ou en enregistrant l'état de l'instance. Cependant, ces options peuvent créer des problèmes. Dans la plupart des cas, vous pouvez utiliser rememberSaveable
, mais vous devrez peut-être conserver la logique dans ou près des composables. Quand une application se développe, il convient de séparer les données et la logique des composables. Dans cet atelier de programmation, vous allez découvrir une méthode efficace pour concevoir votre application et préserver ses données lors des modifications de configuration grâce à la bibliothèque Android Jetpack, à ViewModel
et aux principes fondamentaux de l'architecture des applications Android.
Les bibliothèques Android Jetpack vous permettent de développer plus facilement des applications Android performantes. Elles vous aident à suivre les bonnes pratiques, vous évitent d'avoir à écrire du code récurrent et simplifient les tâches complexes. Vous pouvez ainsi vous concentrer sur le code qui vous intéresse, comme la logique de l'application.
L'architecture des applications est l'ensemble des règles qui régit la conception d'une application. Comme le plan d'une maison, votre architecture fournit la structure de votre application. Avec une bonne architecture, votre code peut être robuste, flexible, évolutif, testable et facile à gérer au cours des années à venir. Le Guide de l'architecture des applications fournit des recommandations sur l'architecture des applications et les bonnes pratiques en la matière.
Dans cet atelier de programmation, vous allez apprendre à utiliser ViewModel
, l'un des composants d'architecture des bibliothèques Android Jetpack capables de stocker les données de vos applications. Si le framework détruit et recrée les activités lors d'une modification de configuration, ou lorsque d'autres événements surviennent, les données stockées ne sont pas perdues. Cependant, les données seront perdues si l'activité est détruite suite à l'arrêt du processus. Le ViewModel
ne met en cache les données que via des recréations liées à une activité rapide.
Conditions préalables
- Vous maîtrisez Kotlin, y compris les fonctions, les lambdas et les composables sans état.
- Vous disposez de connaissances de base en création de mises en page dans Jetpack Compose.
- Vous disposez de connaissances de base en Material Design.
Points abordés
- Présentation de l'architecture des applications Android
- Utiliser la classe
ViewModel
dans votre application - Comment conserver les données de l'interface utilisateur en modifiant la configuration de l'appareil à l'aide de
ViewModel
Objectifs de l'atelier
- Créer une application de jeu Unscramble, dans laquelle l'utilisateur doit retrouver un mot à partir de lettres mélangées.
Ce dont vous avez besoin
- La dernière version d'Android Studio
- Une connexion Internet pour télécharger le code de démarrage
2. Présentation de l'application
Présentation du jeu
L'application Unscramble est un jeu en solo qui repose sur le principe des anagrammes. L'application affiche un mot dont les lettres ont été mélangées, et le joueur doit retrouver le mot en utilisant toutes les lettres affichées. Si le mot est correct, le joueur marque des points. Dans le cas contraire, il peut retenter sa chance. L'application propose également une option permettant d'ignorer un mot. Dans le coin supérieur droit, l'application affiche le nombre de mots qui vous ont déjà été proposés au cours de la partie. Il y a 10 mots à deviner par partie.
Télécharger 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-unscramble.git $ cd basic-android-kotlin-compose-training-unscramble $ git checkout starter
Vous pouvez parcourir le code de démarrage dans le dépôt GitHub Unscramble
.
3. Présentation de l'application de démarrage
Pour vous familiariser avec le code de démarrage, procédez comme suit :
- Dans Android Studio, ouvrez le projet contenant le code de démarrage.
- Exécutez l'appli sur un appareil Android ou sur un émulateur.
- Appuyez sur les boutons Submit (Envoyer) et Skip (Ignorer) pour tester l'appli.
Vous remarquerez des bugs dans l'appli. Le mot mélangé ne s'affiche pas, mais est codé en dur sur "scrambleun", et rien ne se passe lorsque vous appuyez sur ces boutons.
Dans cet atelier de programmation, vous allez implémenter la fonctionnalité de jeu à l'aide de l'architecture des applications Android.
Explication du code de démarrage
Le code de démarrage vous propose une mise en page prédéfinie pour l'écran du jeu. Dans ce parcours, vous allez vous concentrer sur la mise en œuvre de la logique du jeu. Vous utiliserez des composants d'architecture pour implémenter la structure d'application recommandée et résoudre les problèmes mentionnés ci-dessus. Voici une présentation rapide de certains fichiers pour vous aider à vous lancer.
WordsData.kt
Ce fichier contient la liste des mots utilisés dans le jeu, ainsi que des constantes pour le nombre maximal de mots par partie et le nombre de points gagnés par le joueur pour chaque mot trouvé.
package com.example.android.unscramble.data
const val MAX_NO_OF_WORDS = 10
const val SCORE_INCREASE = 20
// Set with all the words for the Game
val allWords: Set<String> =
setOf(
"animal",
"auto",
"anecdote",
"alphabet",
"all",
"awesome",
"arise",
"balloon",
"basket",
"bench",
// ...
"zoology",
"zone",
"zeal"
)
MainActivity.kt
Ce fichier contient principalement du code généré à partir d'un modèle. Le composable GameScreen
est affiché dans le bloc setContent{}
.
GameScreen.kt
Tous les composables de l'interface utilisateur sont définis dans le fichier GameScreen.kt
. Les sections suivantes vous présentent quelques fonctions modulables.
GameStatus
GameStatus
est une fonction composable qui affiche le score du jeu en bas de l'écran. La fonction composable contient un composable Text dans une Card
. Pour l'instant, le score est codé en dur pour être 0
.
// No need to copy, this is included in the starter code.
@Composable
fun GameStatus(score: Int, modifier: Modifier = Modifier) {
Card(
modifier = modifier
) {
Text(
text = stringResource(R.string.score, score),
style = typography.headlineMedium,
modifier = Modifier.padding(8.dp)
)
}
}
GameLayout
GameLayout
est une fonction composable qui affiche la fonctionnalité principale du jeu, qui comprend le mot mélangé, les instructions du jeu et un champ de texte qui accepte les propositions de l'utilisateur.
Notez que le code GameLayout
ci-dessous contient une colonne dans une Card
avec trois éléments "enfant" : le mot mélangé, le texte des instructions et le champ de texte pour le mot OutlinedTextField
de l'utilisateur. Pour le moment, le mot mélangé est codé en dur pour être scrambleun
. Plus tard dans l'atelier, vous implémenterez une fonctionnalité permettant d'afficher un mot du fichier WordsData.kt
.
// No need to copy, this is included in the starter code.
@Composable
fun GameLayout(modifier: Modifier = Modifier) {
val mediumPadding = dimensionResource(R.dimen.padding_medium)
Card(
modifier = modifier,
elevation = CardDefaults.cardElevation(defaultElevation = 5.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(mediumPadding),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(mediumPadding)
) {
Text(
modifier = Modifier
.clip(shapes.medium)
.background(colorScheme.surfaceTint)
.padding(horizontal = 10.dp, vertical = 4.dp)
.align(alignment = Alignment.End),
text = stringResource(R.string.word_count, 0),
style = typography.titleMedium,
color = colorScheme.onPrimary
)
Text(
text = "scrambleun",
style = typography.displayMedium
)
Text(
text = stringResource(R.string.instructions),
textAlign = TextAlign.Center,
style = typography.titleMedium
)
OutlinedTextField(
value = "",
singleLine = true,
shape = shapes.large,
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.textFieldColors(containerColor = colorScheme.surface),
onValueChange = { },
label = { Text(stringResource(R.string.enter_your_word)) },
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { }
)
)
}
}
}
Le composable OutlinedTextField
est semblable au composable TextField
des applications vu dans les précédents ateliers.
Il existe deux types de champs de texte :
- Les champs de texte remplis
- Champs de texte encadrés
Les champs de texte encadrés sont moins visibles que les champs de texte remplis. Lorsqu'ils apparaissent au niveau des formulaires, qui réunissent de nombreux champs de texte, leur accentuation réduite facilite la mise en page.
Dans le code de démarrage, OutlinedTextField
ne se met pas à jour lorsque l'utilisateur saisit une proposition. Vous allez mettre à jour cette fonctionnalité au cours de cet atelier.
GameScreen
Le composable GameScreen
contient les fonctions composables GameStatus
et GameLayout
, le titre du jeu, le nombre de mots et les composables des boutons Submit (Envoyer) et Skip (Ignorer).
@Composable
fun GameScreen() {
val mediumPadding = dimensionResource(R.dimen.padding_medium)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(mediumPadding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.app_name),
style = typography.titleLarge,
)
GameLayout(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(mediumPadding)
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(mediumPadding),
verticalArrangement = Arrangement.spacedBy(mediumPadding),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { }
) {
Text(
text = stringResource(R.string.submit),
fontSize = 16.sp
)
}
OutlinedButton(
onClick = { },
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(R.string.skip),
fontSize = 16.sp
)
}
}
GameStatus(score = 0, modifier = Modifier.padding(20.dp))
}
}
Les événements de clic de bouton ne sont pas implémentés dans le code de démarrage. Vous allez implémenter ces événements au cours de cet atelier.
FinalScoreDialog
Le composable FinalScoreDialog
affiche une boîte de dialogue, c'est-à-dire une petite fenêtre qui invite l'utilisateur à effectuer les actions Play Again (Rejouer) ou Exit (Quitter le jeu). Plus tard dans cet atelier de programmation, vous allez implémenter une logique pour afficher cette boîte de dialogue à la fin du jeu.
// No need to copy, this is included in the starter code.
@Composable
private fun FinalScoreDialog(
score: Int,
onPlayAgain: () -> Unit,
modifier: Modifier = Modifier
) {
val activity = (LocalContext.current as Activity)
AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onDismissRequest.
},
title = { Text(text = stringResource(R.string.congratulations)) },
text = { Text(text = stringResource(R.string.you_scored, score)) },
modifier = modifier,
dismissButton = {
TextButton(
onClick = {
activity.finish()
}
) {
Text(text = stringResource(R.string.exit))
}
},
confirmButton = {
TextButton(onClick = onPlayAgain) {
Text(text = stringResource(R.string.play_again))
}
}
)
}
4. Notions fondamentales sur l'architecture des applications
L'architecture d'une application vous fournit les principes fondamentaux qui vous aideront à répartir les responsabilités de l'application dans les différentes classes. Une architecture d'application performante vous permet de faire évoluer votre projet et d'y ajouter des fonctionnalités supplémentaires. L'architecture peut également faciliter le travail en équipe.
Les principes fondamentaux de l'architecture des applications sont la séparation des tâches et le contrôle de l'interface utilisateur à partir d'un modèle.
Séparation des tâches
Selon le principe de conception de séparation des tâches, l'application doit être divisée en classes, chacune ayant des responsabilités distinctes.
Contrôle de l'UI à partir d'un modèle
Le principe de contrôle de l'UI à partir d'un modèle indique que vous devez contrôler votre UI à l'aide d'un modèle, de préférence un modèle persistant. Les modèles sont des composants chargés de gérer les données d'une appli. Ils sont indépendants des éléments d'UI et des composants de votre appli et ne sont donc pas concernés par le cycle de vie de l'appli, ni par les préoccupations qui en découlent.
Architecture d'application recommandée
Compte tenu des principes fondamentaux de l'architecture des applications mentionnés dans la section précédente, chaque application doit comporter au moins deux couches :
- Couche d'interface utilisateur : une couche qui affiche les données de l'application à l'écran, mais qui reste indépendante des données.
- Couche de données : une couche qui stocke, récupère et expose les données de l'application.
Vous pouvez ajouter une autre couche, appelée couche de domaine, pour simplifier et réutiliser les interactions entre l'interface utilisateur et les couches de données. Cette couche est facultative et dépasse le cadre de cet atelier.
Couche d'interface utilisateur
Le rôle de la couche d'interface utilisateur, ou couche de présentation, est d'afficher les données de l'application à l'écran. Chaque fois que les données changent en raison d'une interaction utilisateur (p. ex. l'appui sur un bouton), l'interface utilisateur doit se mettre à jour pour refléter les modifications.
La couche de l'interface utilisateur contient les composants suivants :
- Éléments d'interface utilisateur : les composants qui affichent les données à l'écran. Pour créer ces éléments, utilisez Jetpack Compose.
- Conteneurs d'états : les composants qui contiennent les données, les exposent à l'interface utilisateur et gèrent la logique de l'application. Par exemple, un ViewModel est un conteneur d'état.
ViewModel
Le composant ViewModel
conserve et expose l'état utilisé par l'UI. L'état de l'interface utilisateur correspond aux données d'application transformées par le ViewModel
. ViewModel
permet à votre application de suivre le principe fondamental de l'architecture consistant à contrôler l'UI à partir du modèle.
ViewModel
stocke les données liées à l'application qui ne sont pas détruites lorsque le framework Android détruit et recrée l'activité. Contrairement à l'instance d'activité, les objets ViewModel
ne sont pas détruits. L'application conserve automatiquement les objets ViewModel
lors des modifications de configuration pour que les données détenues par ces objets soient immédiatement disponibles après la recomposition.
Pour implémenter ViewModel
dans votre application, étendez la classe ViewModel
, qui provient de la bibliothèque de composants d'architecture, et utilisez-la pour stocker les données de l'application.
État de l'interface utilisateur
L'interface utilisateur correspond à ce que l'utilisateur voit, tandis que l'état de l'interface utilisateur correspond à ce qu'il devrait voir selon l'application. L'interface utilisateur est la représentation visuelle de son état. Toute modification apportée à l'état de l'UI est immédiatement répercutée dans l'interface.
L'UI est le résultat d'une liaison entre des éléments d'interface utilisateur et un état d'interface utilisateur.
// Example of UI state definition, do not copy over
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)
Immuabilité
Dans l'exemple ci-dessus, la définition de l'état de l'interface utilisateur est immuable. Les objets immuables vous assurent que plusieurs sources ne modifient pas instantanément l'état de l'appli. Cette protection libère l'interface utilisateur, ce qui lui permet de se concentrer sur un seul rôle : lire l'état et mettre à jour les éléments de l'interface utilisateur en conséquence. Vous ne devez donc jamais modifier directement l'état de l'UI, sauf si l'UI elle-même est la seule source de ses données. Si vous ne respectez pas ce principe, alors il existera plusieurs sources de vérité pour une même information, ce qui entraînera des incohérences au niveau des données et des bugs.
5. Ajouter un ViewModel
Dans cette tâche, vous allez ajouter un ViewModel
à votre application pour y stocker l'état de l'UI de votre jeu (mot mélangé, nombre de mots et score). Pour résoudre le problème dans le code de démarrage identifié à la section précédente, vous devez enregistrer les données du jeu dans ViewModel
.
- Ouvrez
build.gradle.kts (Module :app)
, faites défiler la page jusqu'au blocdependencies
et ajoutez la dépendance suivante pourViewModel
. Cette dépendance permet d'ajouter le ViewModel tenant compte du cycle de vie à votre appli Compose.
dependencies {
// other dependencies
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
//...
}
- Dans le package
ui
, créez une classe/un fichier Kotlin appeléGameViewModel
. Vous l'étendrez à partir de la classeViewModel
.
import androidx.lifecycle.ViewModel
class GameViewModel : ViewModel() {
}
- Dans le package
ui
, ajoutez une classe de modèle pour l'UI d'état, appeléeGameUiState
. Transformez-la en une classe de données et ajoutez une variable pour le mot mélangé.
data class GameUiState(
val currentScrambledWord: String = ""
)
StateFlow
StateFlow
est un flux observable de conteneur de données qui émet les mises à jour de l'état actuel et du nouvel état. Sa propriété value
reflète la valeur de l'état actuel. Pour mettre à jour l'état et l'envoyer au flux, attribuez une nouvelle valeur à la propriété de valeur de la classe MutableStateFlow
.
Dans Android, StateFlow
fonctionne bien avec les classes qui doivent conserver un état immuable observable.
Un StateFlow
peut être exposé à partir de GameUiState
afin que les composables puissent écouter les mises à jour de l'état de l'interface utilisateur et faire en sorte que l'état de l'écran survive aux changements de configuration.
Dans la classe GameViewModel
, ajoutez la propriété _uiState
suivante.
import kotlinx.coroutines.flow.MutableStateFlow
// Game UI state
private val _uiState = MutableStateFlow(GameUiState())
Propriété de support
Une propriété de support vous permet de renvoyer un élément d'un getter qui n'est pas l'objet exact.
Pour la propriété var
, le framework Kotlin génère des getters et des setters.
Pour les méthodes getter et setter, vous pouvez forcer l'une de ces méthodes (ou les deux) afin de personnaliser leur comportement. Pour implémenter une propriété de support, vous devez forcer la méthode getter afin de renvoyer une version en lecture seule de vos données. L'exemple suivant vous montre une propriété de support.
//Example code, no need to copy over
// Declare private mutable variable that can only be modified
// within the class it is declared.
private var _count = 0
// Declare another public immutable field and override its getter method.
// Return the private property's value in the getter method.
// When count is accessed, the get() function is called and
// the value of _count is returned.
val count: Int
get() = _count
Imaginons que vous souhaitiez que les données de l'application soient privées pour ViewModel
:
Dans la classe ViewModel
:
- La propriété
_count
estprivate
(privée) et modifiable. Par conséquent, elle n'est accessible et modifiable que dans la classeViewModel
.
En dehors de la classe ViewModel
:
- Dans Kotlin, le modificateur de visibilité par défaut est
public
.count
est donc public et accessible à partir d'autres classes, comme les contrôleurs d'interface utilisateur. Un typeval
ne peut pas avoir de setter. Comme il est immuable et en lecture seule, vous pouvez uniquement remplacer la méthodeget()
. Lorsqu'une classe externe accède à cette propriété, elle renvoie la valeur de_count
, qui n'est pas modifiable. Cette propriété de support protège les données de l'application à l'intérieur deViewModel
contre les modifications indésirables et non sécurisées effectuées par les classes externes. Elle permet également aux appelants externes d'accéder à sa valeur de façon sécurisée.
- Dans le fichier
GameViewModel.kt
, ajoutez une propriété de support àuiState
nommée_uiState
. Nommez la propriétéuiState
et utilisez le typeStateFlow<GameUiState>
.
_uiState
est désormais accessible et modifiable uniquement dans GameViewModel
. L'interface utilisateur peut lire sa valeur à l'aide de la propriété en lecture seule, uiState
. Vous pourrez corriger l'erreur d'initialisation à l'étape suivante.
import kotlinx.coroutines.flow.StateFlow
// Game UI state
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(GameUiState())
val uiState: StateFlow<GameUiState>
- Définissez
uiState
sur_uiState.asStateFlow()
.
Le asStateFlow()
transforme ce flux d'état modifiable en un flux d'état en lecture seule.
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
// Game UI state
private val _uiState = MutableStateFlow(GameUiState())
val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()
Afficher un mot mélangé aléatoire
Dans cette tâche, vous allez ajouter des méthodes d'assistance pour choisir un mot aléatoire dans WordsData.kt
et mélanger les lettres.
- Dans
GameViewModel
, ajoutez une propriété appeléecurrentWord
de typeString
pour enregistrer le mot mélangé actuel.
private lateinit var currentWord: String
- Ajoutez une méthode d'assistance pour choisir un mot aléatoire dans la liste et le lire en mode aléatoire. Nommez-le
pickRandomWordAndShuffle()
sans paramètre d'entrée et faites-lui renvoyer uneString
.
import com.example.unscramble.data.allWords
private fun pickRandomWordAndShuffle(): String {
// Continue picking up a new random word until you get one that hasn't been used before
currentWord = allWords.random()
if (usedWords.contains(currentWord)) {
return pickRandomWordAndShuffle()
} else {
usedWords.add(currentWord)
return shuffleCurrentWord(currentWord)
}
}
Android Studio signale une erreur pour la variable et la fonction non définies.
- Dans
GameViewModel
, ajoutez la propriété suivante après la propriétécurrentWord
pour servir d'ensemble modifiable qui stockera les mots utilisés dans le jeu.
// Set of words used in the game
private var usedWords: MutableSet<String> = mutableSetOf()
- Ajoutez une autre méthode d'assistance pour lire le mot actuel
shuffleCurrentWord()
en mode aléatoire, qui accepte uneString
et renvoie laString
dans un ordre aléatoire.
private fun shuffleCurrentWord(word: String): String {
val tempWord = word.toCharArray()
// Scramble the word
tempWord.shuffle()
while (String(tempWord).equals(word)) {
tempWord.shuffle()
}
return String(tempWord)
}
- Ajoutez une fonction d'assistance appelée
resetGame()
permettant d'initialiser le jeu. Vous utiliserez cette fonction plus tard pour démarrer et redémarrer le jeu. Dans cette fonction, effacez tous les mots de l'ensembleusedWords
et initialisez_uiState
. Choisissez un nouveau mot pourcurrentScrambledWord
avecpickRandomWordAndShuffle()
.
fun resetGame() {
usedWords.clear()
_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
}
- Ajoutez un bloc
init
auGameViewModel
et appelezresetGame()
à partir de celui-ci.
init {
resetGame()
}
Lorsque vous compilez votre appli, vous ne verrez toujours aucune modification dans l'interface utilisateur. Vous ne transmettez pas les données du ViewModel
aux composables de GameScreen
.
6. Structurer votre interface utilisateur Compose
Dans Compose, le seul moyen de mettre à jour l'UI est de modifier l'état de l'appli. Vous pouvez contrôler l'état de l'interface utilisateur. À chaque modification de l'interface utilisateur, Compose recrée les parties de l'arborescence qui ont été modifiées. Les composables peuvent accepter des événements "state" et "expose". Par exemple, un objet TextField
/OutlinedTextField
accepte une valeur et expose un rappel onValueChange
qui demande au gestionnaire de rappel de modifier la valeur.
//Example code no need to copy over
var name by remember { mutableStateOf("") }
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
Étant donné que les composables acceptent des événements "state" et "expose", le modèle de flux de données unidirectionnel s'adapte bien à Jetpack Compose. Ce guide explique comment implémenter le modèle de flux de données unidirectionnel, implémenter des événements et des conteneurs d'état, et utiliser les ViewModel
dans Compose.
Flux de données unidirectionnel
Un flux de données unidirectionnel (UDF) est un modèle de conception dans lequel l'état redescend et les événements remontent. En suivant le flux de données unidirectionnel, vous pouvez dissocier les composables qui affichent l'état dans l'interface utilisateur des parties de votre application qui stockent et modifient l'état.
La boucle de mise à jour de l'UI pour une application utilisant un flux de données unidirectionnel se présente comme suit :
- Événement : une partie de l'interface utilisateur génère un événement et le fait remonter (p. ex. un clic sur le bouton transmis au ViewModel qui va le gérer), ou un événement transmis à partir d'autres couches de votre application (p. ex. un message qui indique l'expiration de la session utilisateur).
- Modification d'état : un gestionnaire d'événements peut modifier l'état.
- État de l'affichage : le conteneur d'état transmet l'état, et l'interface utilisateur l'affiche.
L'utilisation du modèle de flux de données unidirectionnel (UDF) pour l'architecture des applications a les conséquences suivantes :
ViewModel
conserve et expose l'état utilisé par l'UI.- L'état de l'interface utilisateur correspond aux données d'application transformées par
ViewModel
. - L'interface utilisateur informe
ViewModel
des événements d'utilisateurs. ViewModel
gère les actions de l'utilisateur et met à jour l'état.- L'état mis à jour est renvoyé à l'interface utilisateur, puis affiché.
- Ce processus se répète pour tout événement qui provoque une mutation d'état.
Transmettre les données
Transmettez l'instance du ViewModel à l'interface utilisateur, c'est-à-dire de GameViewModel
à GameScreen()
dans le fichier GameScreen.kt
. Dans GameScreen()
, utilisez l'instance du ViewModel pour accéder à uiState
à l'aide de collectAsState()
.
La fonction collectAsState()
collecte les valeurs de ce StateFlow
et représente sa dernière valeur à l'aide de State
. StateFlow.value
est utilisée comme valeur initiale. Chaque fois qu'une nouvelle valeur est ajoutée dans StateFlow
, la valeur renvoyée State
se met à jour, ce qui entraîne la recomposition de chaque utilisation de State.value
.
- Dans la fonction
GameScreen
, transmettez un deuxième argument du typeGameViewModel
avec une valeur par défaut deviewModel()
.
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun GameScreen(
gameViewModel: GameViewModel = viewModel()
) {
// ...
}
- Dans la fonction
GameScreen()
, ajoutez une variable appeléegameUiState
. Utilisez le déléguéby
et appelezcollectAsState()
suruiState
.
Grâce à cette approche, lorsque la valeur uiState
est modifiée, la recomposition a lieu pour les composables utilisant la valeur gameUiState
.
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@Composable
fun GameScreen(
// ...
) {
val gameUiState by gameViewModel.uiState.collectAsState()
// ...
}
- Transmettez
gameUiState.currentScrambledWord
au composableGameLayout()
. Vous ajouterez l'argument à une étape ultérieure. Pour le moment, vous pouvez ignorer l'erreur.
GameLayout(
currentScrambledWord = gameUiState.currentScrambledWord,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(mediumPadding)
)
- Ajoutez
currentScrambledWord
comme autre paramètre à la fonction composableGameLayout()
.
@Composable
fun GameLayout(
currentScrambledWord: String,
modifier: Modifier = Modifier
) {
}
- Mettez à jour la fonction composable
GameLayout()
pour affichercurrentScrambledWord
. Définissez le paramètretext
du premier champ de texte de la colonne surcurrentScrambledWord
.
@Composable
fun GameLayout(
// ...
) {
Column(
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
text = currentScrambledWord,
fontSize = 45.sp,
modifier = modifier.align(Alignment.CenterHorizontally)
)
//...
}
}
- Exécutez et créez l'application. Le mot mélangé doit s'afficher.
Afficher le mot à deviner
Dans le composable GameLayout()
, la mise à jour de la proposition de l'utilisateur est l'un des rappels d'événement qui passent de GameScreen
à ViewModel
. Les données gameViewModel.userGuess
seront transférées de ViewModel
vers GameScreen
.
- Dans le fichier
GameScreen.kt
, dans le composableGameLayout()
, définissezonValueChange
suronUserGuessChanged
etonKeyboardDone()
sur l'action clavieronDone
. Vous corrigerez les erreurs à l'étape suivante.
OutlinedTextField(
value = "",
singleLine = true,
modifier = Modifier.fillMaxWidth(),
onValueChange = onUserGuessChanged,
label = { Text(stringResource(R.string.enter_your_word)) },
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { onKeyboardDone() }
),
- Dans la fonction composable
GameLayout()
, ajoutez deux autres arguments : le lambdaonUserGuessChanged
accepte un argumentString
et ne renvoie rien, tandis queonKeyboardDone
ne prend rien et ne renvoie rien.
@Composable
fun GameLayout(
onUserGuessChanged: (String) -> Unit,
onKeyboardDone: () -> Unit,
currentScrambledWord: String,
modifier: Modifier = Modifier,
) {
}
- Dans l'appel de fonction
GameLayout()
, ajoutez des arguments lambda pouronUserGuessChanged
etonKeyboardDone
.
GameLayout(
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { },
currentScrambledWord = gameUiState.currentScrambledWord,
)
Vous allez bientôt définir la méthode updateUserGuess
dans GameViewModel
.
- Dans le fichier
GameViewModel.kt
, ajoutez une méthode appeléeupdateUserGuess()
qui accepte un argumentString
, le mot proposé par l'utilisateur. Dans la fonction, mettez à jour leuserGuess
avec la valeurguessedWord
transmise.
fun updateUserGuess(guessedWord: String){
userGuess = guessedWord
}
Vous allez ensuite ajouter userGuess
dans le ViewModel.
- Dans le fichier
GameViewModel.kt
, ajoutez une propriété var appeléeuserGuess
. UtilisezmutableStateOf()
pour que Compose observe cette valeur et définisse la valeur initiale sur""
.
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
var userGuess by mutableStateOf("")
private set
- Dans le fichier
GameScreen.kt
, dansGameLayout()
, ajoutez un autre paramètreString
pouruserGuess
. Définissez le paramètrevalue
deOutlinedTextField
suruserGuess
.
fun GameLayout(
currentScrambledWord: String,
userGuess: String,
onUserGuessChanged: (String) -> Unit,
onKeyboardDone: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
//...
OutlinedTextField(
value = userGuess,
//..
)
}
}
- Dans la fonction
GameScreen
, mettez à jour l'appel de fonctionGameLayout()
pour inclure le paramètreuserGuess
.
GameLayout(
currentScrambledWord = gameUiState.currentScrambledWord,
userGuess = gameViewModel.userGuess,
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { },
//...
)
- Créez et exécutez votre application.
- Essayez de deviner le mot et entrez une proposition. Le champ de texte permet d'afficher la proposition de l'utilisateur.
7. Vérifier le mot à deviner et modifier le score
Dans cette tâche, vous implémenterez une méthode permettant de vérifier la proposition de l'utilisateur, puis de mettre à jour le score du jeu ou d'afficher une erreur. Vous mettrez à jour l'interface d'état du jeu pour afficher le nouveau score et le nouveau mot plus tard.
- Dans
GameViewModel
, ajoutez une autre méthode appeléecheckUserGuess()
. - Dans la fonction
checkUserGuess()
, ajoutez un blocif else
pour vérifier que la proposition de l'utilisateur est identique à celle decurrentWord
. RéinitialisezuserGuess
pour vider la chaîne.
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
} else {
}
// Reset user guess
updateUserGuess("")
}
- Si la réponse de l'utilisateur est incorrecte, définissez
isGuessedWordWrong
surtrue
.MutableStateFlow<T>.
update()
met à jourMutableStateFlow.value
avec la valeur spécifiée.
import kotlinx.coroutines.flow.update
if (userGuess.equals(currentWord, ignoreCase = true)) {
} else {
// User's guess is wrong, show an error
_uiState.update { currentState ->
currentState.copy(isGuessedWordWrong = true)
}
}
- Dans la classe
GameUiState
, ajoutez unBoolean
appeléisGuessedWordWrong
et initialisez-le surfalse
.
data class GameUiState(
val currentScrambledWord: String = "",
val isGuessedWordWrong: Boolean = false,
)
Vous transmettez ensuite le rappel d'événement checkUserGuess()
de GameScreen
à ViewModel
lorsque l'utilisateur clique sur le bouton Submit (Envoyer) ou sur la touche "OK" du clavier. Transmettez les données, gameUiState.isGuessedWordWrong
de ViewModel
à GameScreen
, pour définir l'erreur dans le champ de texte.
- Dans le fichier
GameScreen.kt
, à la fin de la fonction composableGameScreen()
, appelezgameViewModel.checkUserGuess()
dans l'expression lambdaonClick
du bouton Submit (Envoyer).
Button(
modifier = modifier
.fillMaxWidth()
.weight(1f)
.padding(start = 8.dp),
onClick = { gameViewModel.checkUserGuess() }
) {
Text(stringResource(R.string.submit))
}
- Dans la fonction composable
GameScreen()
, mettez à jour l'appel de fonctionGameLayout()
pour transmettregameViewModel.checkUserGuess()
dans l'expression lambdaonKeyboardDone
.
GameLayout(
currentScrambledWord = gameUiState.currentScrambledWord,
userGuess = gameViewModel.userGuess,
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { gameViewModel.checkUserGuess() }
)
- Dans la fonction composable
GameLayout()
, ajoutez un paramètre de fonction pourBoolean
:isGuessWrong
. Définissez le paramètreisError
deOutlinedTextField
surisGuessWrong
pour afficher l'erreur dans le champ de texte si la proposition est incorrecte.
fun GameLayout(
currentScrambledWord: String,
isGuessWrong: Boolean,
userGuess: String,
onUserGuessChanged: (String) -> Unit,
onKeyboardDone: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
// ,...
OutlinedTextField(
// ...
isError = isGuessWrong,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { onKeyboardDone() }
),
)
}
}
- Dans la fonction composable
GameScreen()
, mettez à jour l'appel de la fonctionGameLayout()
pour transmettreisGuessWrong
.
GameLayout(
currentScrambledWord = gameUiState.currentScrambledWord,
userGuess = gameViewModel.userGuess,
onUserGuessChanged = { gameViewModel.updateUserGuess(it) },
onKeyboardDone = { gameViewModel.checkUserGuess() },
isGuessWrong = gameUiState.isGuessedWordWrong,
// ...
)
- Créez et exécutez votre application.
- Saisissez une proposition incorrecte et cliquez sur Submit (Envoyer). Notez que le champ de texte devient rouge pour indiquer une erreur.
Le libellé du champ de texte indique toujours "Enter your word" (Saisissez votre mot). Pour le rendre plus ergonomique, vous devez ajouter un texte d'erreur qui indique que le mot est incorrect.
- Dans le fichier
GameScreen.kt
, dans le composableGameLayout()
, mettez à jour le paramètre de libellé du champ de texte en fonction deisGuessWrong
, comme suit :
OutlinedTextField(
// ...
label = {
if (isGuessWrong) {
Text(stringResource(R.string.wrong_guess))
} else {
Text(stringResource(R.string.enter_your_word))
}
},
// ...
)
- Dans le fichier
strings.xml
, ajoutez une chaîne au libellé d'erreur.
<string name="wrong_guess">Wrong Guess!</string>
- Créez et exécutez à nouveau votre application.
- Saisissez une proposition incorrecte et cliquez sur Submit (Envoyer). Observez le libellé d'erreur.
8. Modifier le score et le nombre de mots
Dans cette tâche, vous allez mettre à jour le score et le nombre de mots au fil de la partie de l'utilisateur. Le score doit faire partie de _ uiState
.
- Dans
GameUiState
, ajoutez une variablescore
et initialisez-la avec la valeur zéro.
data class GameUiState(
val currentScrambledWord: String = "",
val isGuessedWordWrong: Boolean = false,
val score: Int = 0
)
- Pour mettre à jour la valeur du score, dans
GameViewModel
, dans la fonctioncheckUserGuess()
, dans la conditionif
, lorsque la réponse de l'utilisateur est correcte, augmentez la valeurscore
.
import com.example.unscramble.data.SCORE_INCREASE
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
// User's guess is correct, increase the score
val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
} else {
//...
}
}
- Dans
GameViewModel
, ajoutez une autre méthode appeléeupdateGameState
pour mettre à jour le score, augmenter le nombre de mots actuel et choisir un nouveau mot dans le fichierWordsData.kt
. Ajoutez unInt
nomméupdatedScore
en guise de paramètre. Mettez à jour les variables d'interface d'état du jeu comme suit :
private fun updateGameState(updatedScore: Int) {
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
currentScrambledWord = pickRandomWordAndShuffle(),
score = updatedScore
)
}
}
- Dans la fonction
checkUserGuess()
, si la réponse de l'utilisateur est correcte, appelezupdateGameState
avec le score mis à jour pour préparer le jeu pour le prochain tour.
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
// User's guess is correct, increase the score
// and call updateGameState() to prepare the game for next round
val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
updateGameState(updatedScore)
} else {
//...
}
}
La fonction checkUserGuess()
terminée doit se présenter comme suit :
fun checkUserGuess() {
if (userGuess.equals(currentWord, ignoreCase = true)) {
// User's guess is correct, increase the score
// and call updateGameState() to prepare the game for next round
val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
updateGameState(updatedScore)
} else {
// User's guess is wrong, show an error
_uiState.update { currentState ->
currentState.copy(isGuessedWordWrong = true)
}
}
// Reset user guess
updateUserGuess("")
}
Ensuite, comme pour le score, vous devez mettre à jour le nombre de mots joués.
- Ajoutez une autre variable pour le nombre indiqué dans
GameUiState
. Appelez-lacurrentWordCount
et initialisez-la à1
.
data class GameUiState(
val currentScrambledWord: String = "",
val currentWordCount: Int = 1,
val score: Int = 0,
val isGuessedWordWrong: Boolean = false,
)
- Dans la fonction
updateGameState()
du fichierGameViewModel.kt
, augmentez le nombre de mots, comme indiqué ci-dessous. La fonctionupdateGameState()
est appelée pour préparer le jeu pour le prochain tour.
private fun updateGameState(updatedScore: Int) {
_uiState.update { currentState ->
currentState.copy(
//...
currentWordCount = currentState.currentWordCount.inc(),
)
}
}
Score de réussite et nombre de mots
Procédez comme suit pour transmettre les données de score et de nombre de mots de ViewModel
à GameScreen
.
- Dans le fichier
GameScreen.kt
, dans la fonction composableGameLayout()
, ajoutez le nombre de mots en tant qu'argument et transmettez les arguments de formatwordCount
à l'élément de texte.
fun GameLayout(
onUserGuessChanged: (String) -> Unit,
onKeyboardDone: () -> Unit,
wordCount: Int,
//...
) {
//...
Card(
//...
) {
Column(
// ...
) {
Text(
//..
text = stringResource(R.string.word_count, wordCount),
style = typography.titleMedium,
color = colorScheme.onPrimary
)
// ...
}
- Mettez à jour l'appel de fonction
GameLayout()
pour inclure le nombre de mots.
GameLayout(
userGuess = gameViewModel.userGuess,
wordCount = gameUiState.currentWordCount,
//...
)
- Dans la fonction composable
GameScreen()
, mettez à jour l'appel de fonctionGameStatus()
pour inclure les paramètresscore
. Transmettez le score à partir degameUiState
.
GameStatus(score = gameUiState.score, modifier = Modifier.padding(20.dp))
- Créez et exécutez l'application.
- Saisissez le mot à deviner et cliquez sur Submit (Envoyer). Le score et le nombre de mots sont mis à jour.
- Cliquez sur Skip (Ignorer). Rien ne change.
Pour implémenter la fonctionnalité "Ignorer", vous devez transmettre le rappel de l'événement "Ignorer" à GameViewModel
.
- Dans le fichier
GameScreen.kt
, dans la fonction modulableGameScreen()
, appelezgameViewModel.skipWord()
dans l'expression lambdaonClick
.
Android Studio affiche une erreur, car vous n'avez pas encore implémenté la fonction. Vous allez corriger cette erreur à l'étape suivante en ajoutant la méthode skipWord()
. Lorsque l'utilisateur ignore un mot, vous devez mettre à jour les variables du jeu et préparer le jeu pour le prochain tour.
OutlinedButton(
onClick = { gameViewModel.skipWord() },
modifier = Modifier.fillMaxWidth()
) {
//...
}
- Dans
GameViewModel
, ajoutez la méthodeskipWord()
. - Dans la fonction
skipWord()
, appelezupdateGameState()
pour transmettre le score et réinitialiser la proposition de l'utilisateur.
fun skipWord() {
updateGameState(_uiState.value.score)
// Reset user guess
updateUserGuess("")
}
- Exécutez votre application et jouez. Vous devriez à présent pouvoir ignorer des mots.
Vous pouvez toujours jouer au-delà de 10 mots. Lors de votre prochaine tâche, vous vous occuperez de la dernière partie du jeu.
9. Gérer le dernier tour du jeu
Dans l'implémentation actuelle, les utilisateurs peuvent ignorer ou jouer au-delà de 10 mots. Dans cette tâche, vous allez ajouter une logique pour terminer le jeu.
Pour implémenter la logique de fin de jeu, vous devez d'abord vérifier si l'utilisateur a atteint le nombre maximal de mots.
- Dans
GameViewModel
, ajoutez un blocif-else
et déplacez le corps de la fonction existante dans le blocelse
. - Ajoutez une condition
if
pour vérifier que la taille deusedWords
est égale àMAX_NO_OF_WORDS
.
import com.example.android.unscramble.data.MAX_NO_OF_WORDS
private fun updateGameState(updatedScore: Int) {
if (usedWords.size == MAX_NO_OF_WORDS){
//Last round in the game
} else{
// Normal round in the game
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
currentScrambledWord = pickRandomWordAndShuffle(),
currentWordCount = currentState.currentWordCount.inc(),
score = updatedScore
)
}
}
}
- Dans le bloc
if
, ajoutez l'indicateurBoolean
isGameOver
et attribuez la valeurtrue
à l'indicateur pour signaler la fin du jeu. - Mettez à jour
score
et réinitialisezisGuessedWordWrong
dans le blocif
. Le code suivant montre à quoi doit ressembler votre fonction :
private fun updateGameState(updatedScore: Int) {
if (usedWords.size == MAX_NO_OF_WORDS){
//Last round in the game, update isGameOver to true, don't pick a new word
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
score = updatedScore,
isGameOver = true
)
}
} else{
// Normal round in the game
_uiState.update { currentState ->
currentState.copy(
isGuessedWordWrong = false,
currentScrambledWord = pickRandomWordAndShuffle(),
currentWordCount = currentState.currentWordCount.inc(),
score = updatedScore
)
}
}
}
- Dans
GameUiState
, ajoutez la variableBoolean
isGameOver
et attribuez-lui ensuite la valeurfalse
.
data class GameUiState(
val currentScrambledWord: String = "",
val currentWordCount: Int = 1,
val score: Int = 0,
val isGuessedWordWrong: Boolean = false,
val isGameOver: Boolean = false
)
- Exécutez votre application et jouez. Vous ne pouvez pas jouer plus de 10 mots.
Une fois la partie terminée, nous vous recommandons d'en informer l'utilisateur et de lui demander s'il souhaite rejouer. Vous implémenterez cette fonctionnalité dans votre prochaine tâche.
Afficher la boîte de dialogue de fin de jeu
Dans cette tâche, vous allez transmettre des données isGameOver
à GameScreen
depuis le ViewModel et l'utiliser pour afficher une boîte de dialogue d'alerte incluant des options pour arrêter ou redémarrer le jeu.
Une boîte de dialogue est une petite fenêtre qui invite l'utilisateur à prendre une décision ou à saisir des informations supplémentaires. Normalement, une boîte de dialogue ne remplit pas tout l'écran, et les utilisateurs doivent réaliser une action avant de pouvoir continuer. Android propose différents types de boîtes de dialogue. Dans cet atelier de programmation, vous découvrirez les boîtes de dialogue d'alerte.
Anatomie d'une boîte de dialogue d'alerte
- Conteneur
- Icône (facultatif)
- Titre (facultatif)
- Texte d'accompagnement
- Séparateur (facultatif)
- Actions
Le fichier GameScreen.kt
du code de démarrage fournit déjà une fonction affichant une boîte de dialogue d'alerte. Celle-ci inclut des options permettant de quitter ou redémarrer le jeu.
@Composable
private fun FinalScoreDialog(
onPlayAgain: () -> Unit,
modifier: Modifier = Modifier
) {
val activity = (LocalContext.current as Activity)
AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onDismissRequest.
},
title = { Text(stringResource(R.string.congratulations)) },
text = { Text(stringResource(R.string.you_scored, 0)) },
modifier = modifier,
dismissButton = {
TextButton(
onClick = {
activity.finish()
}
) {
Text(text = stringResource(R.string.exit))
}
},
confirmButton = {
TextButton(
onClick = {
onPlayAgain()
}
) {
Text(text = stringResource(R.string.play_again))
}
}
)
}
Dans cette fonction, les paramètres title
et text
affichent le titre et le texte d'accompagnement dans la boîte de dialogue d'alerte. dismissButton
et confirmButton
sont les boutons de texte. Dans le paramètre dismissButton
, vous affichez le texte Exit (Quitter) et arrêtez l'application en terminant l'activité. Dans le paramètre confirmButton
, vous redémarrez le jeu et affichez le texte Play Again (Rejouer).
- Dans la fonction
FinalScoreDialog()
du fichierGameScreen.kt
, notez le paramètre du score afin d'afficher le score du jeu dans la boîte de dialogue d'alerte.
@Composable
private fun FinalScoreDialog(
score: Int,
onPlayAgain: () -> Unit,
modifier: Modifier = Modifier
) {
- Dans la fonction
FinalScoreDialog()
, notez l'utilisation de l'expression lambda du paramètretext
pour utiliserscore
comme argument de format du texte de la boîte de dialogue.
text = { Text(stringResource(R.string.you_scored, score)) }
- Dans le fichier
GameScreen.kt
, à la fin de la fonction composableGameScreen()
, après le blocColumn
, ajoutez une conditionif
pour vérifiergameUiState.isGameOver
. - Dans le bloc
if
, affichez la boîte de dialogue d'alerte. AppelezFinalScoreDialog()
en transmettant les élémentsscore
etgameViewModel.resetGame()
pour le rappel de l'événementonPlayAgain
.
if (gameUiState.isGameOver) {
FinalScoreDialog(
score = gameUiState.score,
onPlayAgain = { gameViewModel.resetGame() }
)
}
resetGame()
est un rappel d'événement transmis de GameScreen
à ViewModel
.
- Dans le fichier
GameViewModel.kt
, le rappel de la fonctionresetGame()
initialise_uiState
et choisit un nouveau mot.
fun resetGame() {
usedWords.clear()
_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
}
- Créez et exécutez votre application.
- Jouez au jeu jusqu'à la fin et consultez la boîte de dialogue d'alerte qui affiche les options Exit (Quitter) ou Play Again (Rejouer). Essayez chacune de ces options.
10. L'état dans la rotation des appareils
Dans les précédents ateliers de programmation, vous avez découvert les modifications de configuration dans Android. Lorsqu'une modification de configuration se produit, Android redémarre toute l'activité, en exécutant tous les rappels de démarrage du cycle de vie.
Le ViewModel
stocke les données liées à l'application qui ne sont pas détruites lorsque le framework Android détruit et recrée l'activité. Les objets ViewModel
sont conservés automatiquement et ne sont pas détruits, comme l'instance d'activité lorsque la configuration change. Les données qu'ils contiennent sont immédiatement disponibles après la recomposition.
Dans cette tâche, vous allez vérifier si l'application conserve StateUI lors d'un changement de configuration.
- Exécutez l'application et commencez à jouer. Faites pivoter l'appareil du mode Portrait au mode Paysage, ou inversement.
- Notez que les données enregistrées dans l'interface utilisateur d'état de
ViewModel
sont conservées lors du changement de configuration.
11. 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-unscramble.git $ cd basic-android-kotlin-compose-training-unscramble $ git checkout viewmodel
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 le souhaitez, vous pouvez consulter le code de solution de cet atelier de programmation sur GitHub.
12. Conclusion
Félicitations ! Vous avez terminé l'atelier de programmation. Vous comprenez maintenant que les principes fondamentaux de l'architecture des applications Android recommandent de séparer les classes ayant des responsabilités distinctes et de piloter l'interface utilisateur à partir d'un modèle.
N'oubliez pas de partager le fruit de vos efforts sur les réseaux sociaux avec le hashtag #AndroidBasics.