1. Avant de commencer
Cet atelier de programmation vous présente la notion d'état et la façon dont Jetpack Compose peut l'utiliser et le manipuler.
En pratique, on entend par "état" toute valeur pouvant évoluer au fil du temps dans une application. Cette définition est très vaste et s'applique autant à une base de données qu'à une variable de votre application. Vous vous familiariserez avec les bases de données dans une prochaine leçon. Pour l'instant, rappelez-vous juste qu'une base de données est une collection organisée d'informations structurées, comme les fichiers présents sur votre ordinateur.
Toutes les applications Android présentent des états à l'utilisateur. Voici quelques exemples d'états que vous pouvez trouver dans les applications Android :
- Message qui s'affiche quand une connexion réseau n'a pas pu être établie.
- Formulaires, comme les formulaires d'inscription. L'état peut indiquer que le formulaire a été renseigné et envoyé.
- Commandes tactiles telles que les boutons. L'état peut indiquer que le bouton n'a pas été utilisé, qu'il est en cours d'utilisation, comme sur un écran d'animation, ou qu'il a été utilisé, comme avec une action
onClick
.
Dans cet atelier de programmation, vous découvrirez comment envisager les états et comment les exploiter lorsque vous utilisez Compose. Pour ce faire, vous créerez une application de calcul du pourboire appelée "Tip Time" à l'aide de ces éléments d'interface utilisateur Compose intégrés :
- Un composable
TextField
pour saisir et modifier le texte. - Un composable
Text
pour afficher le texte. - Un composable
Spacer
pour afficher un espace vide entre les éléments d'interface utilisateur.
À la fin de cet atelier de programmation, vous aurez créé un outil interactif de calcul de pourboire. Il déterminera automatiquement le montant du pourboire lorsque vous saisirez le coût du service. Cette image illustre la version finale de l'application :
Conditions préalables
- Vous connaissez les bases de Compose (annotation
@Composable
, entre autres). - Vous connaissez les principes fondamentaux des mises en page Compose, comme les composables de mise en page
Row
etColumn
. - Vous connaissez les bases des modificateurs, tels que la fonction
Modifier.padding()
. - Vous connaissez bien le composable
Text
.
Points abordés
- Rôle des états dans une interface utilisateur
- Utilisation de l'état par Compose pour afficher les données
- Ajout d'une zone de texte à l'application
- Comment hisser un état
Objectifs de l'atelier
- Application de calcul du pourboire appelée "Tip Time" qui détermine le montant du pourboire en fonction du coût du service.
Ce dont vous avez besoin
- Un ordinateur avec accès à Internet et un navigateur Web
- Connaissance de Kotlin
- La dernière version d'Android Studio
2. Premiers pas
- Découvrez l'outil Google de calcul des pourboires en ligne. Notez qu'il ne s'agit que d'un exemple. Il ne s'agit pas de l'application Android que vous créerez dans ce cours.
- Saisissez des valeurs différentes dans les champs Bill (Facture) et Tip % (Pourcentage de pourboire). Les valeurs du pourboire et les montants totaux changent.
Notez que lorsque vous saisissez les valeurs, le pourboire et le total sont mis à jour. À la fin de cet atelier, vous développerez une application de calcul de pourboire similaire sur Android.
Dans ce parcours, vous créerez une simple application Android de calcul de pourboire.
Les développeurs travaillent souvent de cette manière : ils préparent une version simple et opérationnelle de l'application (même si elle ne semble pas très esthétique), puis ils y ajoutent des fonctionnalités et la rendent plus agréable à l'œil par la suite.
À la fin de cet atelier de programmation, votre application de calcul de pourboire ressemblera à ces captures d'écran. Lorsque l'utilisateur saisit un montant de facture, votre application affiche une suggestion de pourboire. Pour le moment, le pourcentage de pourboire est codé en dur sur une valeur correspondant à 15 %. Dans l'atelier de programmation suivant, vous continuerez à travailler sur votre application et ajouterez d'autres fonctionnalités, comme la personnalisation du pourcentage de pourboire.
3. Obtenir le code de démarrage
Le code de démarrage est le code prédéfini qui peut servir de point de départ pour un nouveau projet. Il peut également vous aider à vous concentrer sur les nouveaux concepts présentés dans cet atelier de programmation.
Pour utiliser le code de démarrage, commencez par le télécharger ici :
Vous pouvez également cloner le dépôt GitHub du code :
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout starter
Vous pouvez parcourir le code dans le dépôt GitHub TipTime
.
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'application sur un appareil Android ou sur un émulateur.
- Deux composants de texte s'affichent. L'un sert simplement de libellé, et l'autre affiche le montant du pourboire.
Explication du code de démarrage
Le code de démarrage contient déjà les composables de texte. Dans cet atelier, vous allez ajouter un champ de texte pour recevoir les entrées des utilisateurs. Voici une présentation rapide de certains fichiers pour vous aider à vous lancer.
res > values > strings.xml
<resources>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
Il s'agit du fichier string.xml
dans les ressources. Il contient toutes les chaînes que vous utiliserez dans cette application.
MainActivity
Ce fichier contient principalement du code généré à partir du modèle, ainsi que les fonctions suivantes :
- La fonction
TipTimeLayout()
contient un élémentColumn
avec les deux composables de texte que vous voyez dans les captures d'écran. Un composablespacer
permet également d'ajouter de l'espace pour des raisons esthétiques. - La fonction
calculateTip()
reçoit le montant de la facture et calcule un pourboire de 15 %. Le paramètretipPercent
est défini sur une valeur d'argument par défaut de15.0
. Par conséquent, le pourboire par défaut est actuellement de 15 %. Dans l'atelier de programmation suivant, le montant du pourboire dépendra du choix de l'utilisateur.
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
val tip = tipPercent / 100 * amount
return NumberFormat.getCurrencyInstance().format(tip)
}
Dans le bloc Surface()
de la fonction onCreate()
, la fonction TipTimeLayout()
est appelée. La mise en page de l'application s'affiche sur l'appareil ou l'émulateur.
override fun onCreate(savedInstanceState: Bundle?) {
//...
setContent {
TipTimeTheme {
Surface(
//...
) {
TipTimeLayout()
}
}
}
}
Dans le bloc TipTimeTheme
de la fonction TipTimeLayoutPreview()
, la fonction TipTimeLayout()
est appelée. La mise en page de l'application s'affiche dans Design (Conception) et dans le volet Split (Diviser).
@Preview(showBackground = true)
@Composable
fun TipTimeLayoutPreview() {
TipTimeTheme {
TipTimeLayout()
}
}
Recevoir les entrées de l'utilisateur
Dans cette section, vous ajouterez l'élément d'interface qui permettra à l'utilisateur de saisir le montant de la facture dans l'application, comme illustré dans l'image ci-dessous :
Votre application utilise un style et un thème personnalisés.
Les styles et les thèmes des ensembles d'attributs qui spécifient l'apparence d'un seul élément d'interface utilisateur. Un style peut par exemple spécifier la couleur de la police, sa taille et sa couleur d'arrière-plan, et ces valeurs seront appliquées à l'ensemble de l'application. Dans d'autres ateliers de programmation, nous verrons comment implémenter ces éléments dans votre application. Pour l'instant, cela a déjà été fait afin d'obtenir un rendu plus esthétique.
Pour mieux visualiser la différence, voici une comparaison côte à côte du rendu final de l'application avec et sans thème personnalisé.
Sans thème personnalisé. | Avec un thème personnalisé. |
La fonction composable TextField
permet à l'utilisateur de taper dans une application. Dans l'image ci-dessous, vous pouvez voir par exemple la zone de texte sur l'écran de connexion de l'application Gmail :
Ajoutez le composable TextField
à l'application :
- Dans le fichier
MainActivity.kt
, ajoutez une fonction composableEditNumberField()
qui accepte un paramètreModifier
. - Dans le corps de la fonction
EditNumberField()
, sousTipTimeLayout()
, ajoutez un élémentTextField
qui accepte un paramètre nommévalue
défini sur une chaîne vide et un paramètre nomméonValueChange
défini sur une expression lambda vide :
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
)
}
- Notez les paramètres que vous avez transmis :
- Le paramètre
value
est une zone de texte qui affiche la valeur de chaîne que vous transmettez ici. - Le paramètre
onValueChange
est le rappel lambda qui est déclenché lorsque l'utilisateur saisit du texte dans la zone à cet effet.
- Importez cette fonction :
import androidx.compose.material3.TextField
- Dans le composable
TipTimeLayout()
, sur la ligne située après la première fonction composable de texte, appelez la fonctionEditNumberField()
en transmettant le modificateur suivant :
import androidx.compose.foundation.layout.fillMaxWidth
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
...
)
EditNumberField(modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth())
Text(
...
)
...
}
}
La zone de texte s'affiche à l'écran.
- Dans le volet Design (Conception), vous devriez voir le texte
Calculate Tip
, une zone de texte vide et le composable de texteTip Amount
.
4. Utiliser l'état dans Compose
Dans une application, l'état correspond à toute valeur susceptible de changer au fil du temps. Dans cette activité, il s'agit du montant de la facture.
Ajoutez une variable pour stocker cet état :
- Au début de la fonction
EditNumberField()
, utilisez le mot cléval
pour ajouter une variableamountInput
et lui attribuer la valeur"0"
:
val amountInput = "0"
Il s'agit de l'état de l'application correspondant au montant de la facture.
- Définissez le paramètre nommé
value
sur une valeuramountInput
:
TextField(
value = amountInput,
onValueChange = {},
)
- Vérifiez l'aperçu. La zone de texte affiche la valeur définie pour la variable d'état, comme illustré dans cette image :
- Exécutez l'application dans l'émulateur et essayez de saisir une autre valeur. L'état codé en dur reste le même, car le composable
TextField
ne se met pas à jour tout seul. Il se met à jour lorsque son paramètrevalue
change, lequel est défini sur la propriétéamountInput
.
La variable amountInput
représente l'état de la zone de texte. Un état codé en dur n'est pas utile, car il ne peut pas être modifié et ne reflète pas l'entrée utilisateur. Vous devez mettre à jour l'état de l'application lorsque l'utilisateur modifie le montant de la facture.
5. Composition
Les composables d'une application décrivent une interface utilisateur qui affiche une colonne de texte, un espace vide et une zone de texte. Le texte affiche un élément Calculate Tip
, et la zone de texte affiche une valeur 0
ou toute valeur par défaut.
Compose est un framework d'interface utilisateur déclaratif, ce qui signifie que vous déclarez la façon dont l'UI doit apparaître dans le code. Si vous souhaitez que la zone de texte affiche initialement la valeur 100
, vous devez définir la valeur 100
comme valeur initiale dans le code des composables.
Que se passe-t-il si vous souhaitez que votre interface utilisateur change pendant que l'application est en cours d'exécution ou lorsque l'utilisateur interagit avec elle ? Par exemple, si vous souhaitez remplacer la variable amountInput
par la valeur saisie par l'utilisateur et l'afficher dans la zone de texte ? C'est là qu'intervient un processus appelé recomposition, qui permet de mettre à jour la composition de l'application.
Le champ Composition est une description de l'interface utilisateur créée par Compose lorsqu'il exécute des composables. Les applications Compose appellent des fonctions modulables pour convertir les données en UI. En cas de changement d'état, Compose réexécute les fonctions modulables concernées avec le nouvel état, ce qui crée une UI mise à jour. Ce processus est appelé recomposition. Compose planifie une recomposition pour vous.
Lorsque Compose exécute vos composables pour la première fois lors de la composition initiale, il suit les composables que vous appelez pour décrire votre UI dans une composition. La recomposition désigne le moment où Compose réexécute les composables qui ont pu changer en réponse à des modifications de données, puis met à jour la composition pour refléter ces modifications.
La composition ne peut être produite que par une composition initiale et mise à jour par une recomposition. Le seul moyen de modifier la composition est la recomposition. Pour ce faire, Compose doit connaître l'état à suivre. Ainsi, lorsqu'il reçoit une mise à jour, il peut planifier la recomposition. Dans votre cas, il s'agit de la variable amountInput
. Chaque fois que sa valeur changera, Compose programmera une recomposition.
Vous pouvez utiliser les types State
et MutableState
dans Compose pour rendre l'état observable ou suivi par Compose. Le type State
est immuable. Vous ne pouvez donc que lire sa valeur, tandis que le type MutableState
est modifiable. Vous pouvez utiliser la fonction mutableStateOf()
pour créer un élément MutableState
observable. Elle reçoit une valeur initiale en tant que paramètre encapsulé dans un objet State
, ce qui rend son élément value
observable.
La valeur renvoyée par la fonction mutableStateOf()
:
- contient l'état, qui correspond au montant de la facture ;
- est modifiable, de sorte que sa valeur peut être ajustée ;
- est observable, de sorte que Compose observe toute modification apportée à la valeur et déclenche une recomposition pour mettre à jour l'UI.
Ajoutez un état de coût de service :
- Dans la fonction
EditNumberField()
, remplacez le mot cléval
avant la variable d'étatamountInput
parvar
:
var amountInput = "0"
Ce changement rend amountInput
modifiable.
- Utilisez le type
MutableState<String>
au lieu de la variableString
codée en dur pour que Compose sache qu'il doit suivre l'étatamountInput
, puis transmettre une chaîne"0"
, qui correspond à la valeur par défaut initiale de la variable d'étatamountInput
:
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
var amountInput: MutableState<String> = mutableStateOf("0")
L'initialisation de amountInput
peut également être écrite comme suit avec l'inférence de type :
var amountInput = mutableStateOf("0")
La fonction mutableStateOf()
reçoit une valeur "0"
initiale en tant qu'argument, ce qui rend amountInput
observable. Un avertissement de compilation est alors généré dans Android Studio, mais vous résoudrez bientôt ce problème :
Creating a state object during composition without using remember.
- Dans la fonction composable
TextField
, utilisez la propriétéamountInput.value
:
TextField(
value = amountInput.value,
onValueChange = {},
modifier = modifier
)
Compose effectue le suivi de chaque composable qui lit les propriétés d'état value
et déclenche une recomposition lorsque son élément value
change.
Le rappel onValueChange
est déclenché lorsque l'entrée de la zone de texte change. Dans l'expression lambda, la variable it
contient la nouvelle valeur.
- Dans l'expression lambda du paramètre nommé
onValueChange
, définissez la propriétéamountInput.value
sur la variableit
:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput = mutableStateOf("0")
TextField(
value = amountInput.value,
onValueChange = { amountInput.value = it },
modifier = modifier
)
}
Vous mettez à jour l'état de TextField
(à savoir, de la variable amountInput
) lorsque TextField
vous informe d'une modification du texte via la fonction de rappel onValueChange
.
- Exécutez l'application et saisissez du texte dans la zone de texte. Comme l'illustre cette image, la zone de texte affiche toujours une valeur
0
:
Lorsque l'utilisateur saisit du texte dans la zone à cet effet, le rappel onValueChange
est appelé et la variable amountInput
est remplacée par la nouvelle valeur. L'état amountInput
est suivi par Compose. Dès lors, lorsque sa valeur changera, la recomposition sera planifiée, et la fonction modulable EditNumberField()
sera réexécutée. Dans cette fonction modulable, la variable amountInput
est réinitialisée et revient donc à sa valeur 0
initiale. Ainsi, la zone de texte affiche une valeur 0
.
Avec le code que vous avez ajouté, les changements d'état entraînent la planification des recompositions.
Cependant, vous devez conserver la valeur de la variable amountInput
lors des recompositions pour qu'elle ne soit pas réinitialisée et remplacée par une valeur 0
chaque fois que la fonction EditNumberField()
effectue une recomposition. Vous résoudrez ce problème dans la section suivante.
6. Utiliser la fonction "remember" pour enregistrer l'état
Les méthodes de composables peuvent être appelées plusieurs fois en raison de la recomposition. Le composable réinitialise son état lors de la recomposition s'il n'est pas enregistré.
Les fonctions modulables peuvent stocker un objet lors des recompositions à l'aide de la fonction remember
. Une valeur calculée par la fonction remember
est stockée dans la composition lors de la composition initiale, et la valeur stockée est renvoyée lors de la recomposition. Les fonctions remember
et mutableStateOf
sont généralement utilisées conjointement dans des fonctions modulables pour que l'état et ses mises à jour soient correctement reflétés dans l'interface utilisateur.
Utilisez la fonction remember
dans la fonction EditNumberField()
:
- Dans la fonction
EditNumberField()
, initialisez la variableamountInput
avec le délégué de propriété Kotlinremember
deby
en encadrant l'appel de la fonctionmutableStateOf
()
avecremember
. - Dans la fonction
mutableStateOf
()
, transmettez une chaîne vide au lieu d'une chaîne"0"
statique :
var amountInput by remember { mutableStateOf("") }
La chaîne vide correspond désormais à la valeur par défaut initiale de la variable amountInput
. by
est une délégation de propriété de Kotlin. Les fonctions "getter" et "setter" par défaut de la propriété amountInput
sont déléguées aux fonctions "getter" et "setter" de la classe remember
, respectivement.
- Importez ces fonctions :
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
L'ajout des importations "getter" et "setter" du délégué vous permet de lire et de définir amountInput
sans faire référence à la propriété value
de MutableState
.
La fonction EditNumberField()
mise à jour devrait se présenter comme suit :
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = modifier
)
}
- Exécutez l'application et saisissez du texte dans la zone à cet effet. Le texte que vous avez saisi devrait maintenant s'afficher.
7. Changement d'état et recomposition à l'œuvre
Dans cette section, vous définirez un point d'arrêt et déboguerez la fonction modulable EditNumberField()
pour voir comment fonctionnent la composition et la recomposition initiales.
Définissez un point d'arrêt et déboguez l'application sur un émulateur ou un appareil :
- Dans la fonction
EditNumberField()
, à côté du paramètre nomméonValueChange
, définissez un point d'arrêt de ligne. - Dans le menu de navigation, cliquez sur Déboguer l'application. L'application se lance sur l'émulateur ou l'appareil. L'exécution de votre application s'interrompt pour la première fois lorsque l'élément
TextField
est créé.
- Dans le volet Débogage, cliquez sur Reprendre le programme. La zone de texte est créée.
- Sur l'émulateur ou l'appareil, saisissez une lettre dans la zone de texte. L'exécution de votre application s'interrompt de nouveau lorsqu'elle atteint le point d'arrêt que vous avez défini.
Lorsque vous saisissez le texte, le rappel onValueChange
est appelé. Dans le lambda it
, vous avez la nouvelle valeur que vous avez saisie au clavier.
Une fois que la valeur "it" est attribuée à amountInput
, Compose déclenche la recomposition avec les nouvelles informations, étant donné que la valeur observable a changé.
- Dans le volet Débogage, cliquez sur Reprendre le programme. Le texte saisi dans l'émulateur ou sur l'appareil s'affiche à côté de la ligne avec le point d'arrêt, comme illustré sur cette image :
Il s'agit de l'état du champ de texte.
- Cliquez sur Resume Program (Reprendre le programme). La valeur saisie s'affiche sur l'émulateur ou l'appareil.
8. Modifier l'apparence
Dans la section précédente, vous avez rendu le champ de texte fonctionnel. Dans cette section, vous améliorerez l'interface utilisateur.
Ajouter un libellé à la zone de texte
Chaque zone de texte doit comporter un libellé indiquant aux utilisateurs les informations qu'ils peuvent saisir. Dans la première partie de l'exemple d'image suivant, le libellé se trouve au milieu d'un champ de texte et est aligné avec la ligne d'entrée. Dans la deuxième partie de l'exemple d'image suivant, le libellé se place plus haut dans la zone de texte lorsque l'utilisateur clique dans la zone pour saisir du texte. Pour en savoir plus sur l'anatomie des champs de texte, consultez Anatomie.
Modifiez la fonction EditNumberField()
pour ajouter un libellé au champ de texte :
- Dans la fonction composable
TextField()
de la fonctionEditNumberField()
, ajoutez un ensemble de paramètreslabel
à une expression lambda vide :
TextField(
//...
label = { }
)
- Dans l'expression lambda, appelez la fonction
Text()
qui accepte une ressourcestringResource
(R.string.
bill_amount
)
:
label = { Text(stringResource(R.string.bill_amount)) },
- Dans la fonction composable
TextField()
, ajoutez un paramètre nommésingleLine
défini sur la valeurtrue
:
TextField(
// ...
singleLine = true,
)
La zone de texte est alors condensée en une seule ligne à faire défiler horizontalement, à partir de plusieurs lignes.
- Ajoutez le paramètre
keyboardOptions
défini surKeyboardOptions()
:
import androidx.compose.foundation.text.KeyboardOptions
TextField(
// ...
keyboardOptions = KeyboardOptions(),
)
Android permet de configurer le clavier à l'écran afin de permettre la saisie de chiffres, d'adresses e-mail, d'URL et de mots de passe, pour ne citer que quelques exemples. Pour en savoir plus sur les autres types de claviers, consultez KeyboardType.
- Définissez le clavier numérique comme type de clavier pour saisir des chiffres. Transmettez à la fonction
KeyboardOptions
un paramètrekeyboardType
défini surKeyboardType.Number
:
import androidx.compose.ui.text.input.KeyboardType
TextField(
// ...
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
Une fois terminée, la fonction EditNumberField()
devrait ressembler à ceci :
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}
- Exécutez l'application.
Vous pouvez voir les modifications apportées au clavier dans cette capture d'écran :
9. Afficher le montant du pourboire
Dans cette section, vous implémenterez la fonctionnalité principale de l'application, qui consiste à calculer et à afficher le montant du pourboire.
Dans le fichier MainActivity.kt
, une fonction private
(privée) calculateTip()
vous est fournie dans le code de démarrage. Vous l'utiliserez pour calculer le montant du pourboire comme suit :
private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
val tip = tipPercent / 100 * amount
return NumberFormat.getCurrencyInstance().format(tip)
}
Dans la méthode ci-dessus, vous utilisez NumberFormat
pour appliquer un format monétaire au pourboire.
Votre application peut maintenant calculer le pourboire, mais vous devez encore le mettre en forme et l'afficher avec la classe adaptée.
Utiliser la fonction calculateTip()
Le texte saisi par l'utilisateur dans le composable du champ de texte est renvoyé à la fonction de rappel onValueChange
en tant que String
, même si l'utilisateur a saisi un nombre. Pour résoudre ce problème, vous devez convertir la valeur amountInput
, qui correspond au montant saisi par l'utilisateur.
- Dans la fonction composable
EditNumberField()
, créez une variable appeléeamount
après la définition deamountInput
. Appelez la fonctiontoDoubleOrNull
sur la variableamountInput
pour la convertir deString
enDouble
:
val amount = amountInput.toDoubleOrNull()
La fonction toDoubleOrNull()
est une fonction Kotlin prédéfinie qui analyse une chaîne en tant que nombre Double
et renvoie le résultat, ou null
si la chaîne ne correspond pas à une représentation valide d'un nombre.
- À la fin de l'instruction, ajoutez un opérateur Elvis
?:
qui renverra une valeur0.0
lorsque la valeur deamountInput
sera nulle :
val amount = amountInput.toDoubleOrNull() ?: 0.0
- Après la variable
amount
, créez une autre variableval
appeléetip
. Initialisez-la avec l'élémentcalculateTip()
, en transmettant le paramètreamount
.
val tip = calculateTip(amount)
La fonction EditNumberField()
devrait se présenter comme suit :
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
TextField(
value = amountInput,
onValueChange = { amountInput = it },
label = { Text(stringResource(R.string.bill_amount)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
Afficher le montant calculé du pourboire
Vous avez écrit la fonction permettant de calculer le montant du pourboire. L'étape suivante consiste à afficher le montant calculé :
- Dans la fonction
TipTimeLayout()
, à la fin du blocColumn()
, un composable de texte affiche$0.00
. Vous devez modifier cette valeur pour qu'elle corresponde au montant calculé du pourboire.
@Composable
fun TipTimeLayout() {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// ...
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
// ...
}
}
Vous devez accéder à la variable amountInput
dans la fonction TipTimeLayout()
pour calculer et afficher le montant du pourboire, mais la variable amountInput
correspond à l'état du champ de texte défini dans la fonction composable EditNumberField()
. Vous ne pouvez pas encore l'appeler depuis la fonction TipTimeLayout()
. Cette image illustre la structure du code :
Cette structure ne vous permet pas d'afficher le montant du pourboire dans le nouveau composable Text
, car le composable Text
doit accéder à la variable amount
calculée à partir de la variable amountInput
. Vous devez exposer la variable amount
à la fonction TipTimeLayout()
. Cette image illustre la structure de code souhaitée, qui rend le composable EditNumberField()
sans état :
Ce système est appelé hissage d'état. Dans la section suivante, vous allez hisser l'état d'un composable afin de le rendre sans état.
10. Hissage d'état
Dans cette section, vous allez apprendre à choisir où définir l'état de sorte à pouvoir réutiliser et partager vos composables.
Dans une fonction modulable, vous pouvez définir des variables qui contiennent un état à afficher dans l'interface utilisateur. Par exemple, vous avez défini la variable amountInput
comme état dans le composable EditNumberField()
.
Lorsque l'application devient plus complexe et que d'autres composables ont besoin d'accéder à l'état au sein de ce composable EditNumberField()
, vous devez envisager de hisser ou extraire l'état de la fonction modulable EditNumberField()
.
Comprendre les composables avec ou sans état
Vous devez hisser l'état dans les cas suivants :
- Vous devez partager l'état avec plusieurs fonctions modulables.
- Vous devez créer un composable sans état qui pourra être réutilisé dans votre application.
Lorsque vous extrayez l'état d'une fonction composable, la fonction composable obtenue est appelée sans état. Autrement dit, les fonctions composables peuvent être rendues sans état si l'on extrait leur état.
Un composable sans état est un composable qui n'a pas d'état. En d'autres termes, il ne peut détenir, définir, ni modifier aucun nouvel état. En revanche, un composable avec état possède un état qui peut changer au fil du temps.
Le hissage d'état est un modèle qui consiste à faire remonter un état vers son appelant pour obtenir un composant sans état.
Lorsqu'il est appliqué à des composables, cela implique souvent d'introduire deux paramètres dans le composable :
- Un paramètre
value: T
, qui correspond à la valeur actuelle à afficher. - Un paramètre
onValueChange: (T) -> Unit
lambda de rappel, qui est déclenché lorsque la valeur change afin que l'état puisse être mis à jour ailleurs, par exemple lorsqu'un utilisateur saisit du texte dans la zone de texte.
Hissez l'état dans la fonction EditNumberField()
:
- Mettez à jour la définition de la fonction
EditNumberField()
pour hisser l'état en ajoutant les paramètresvalue
etonValueChange
:
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
//...
Le paramètre value
est de type String
, et le paramètre onValueChange
est de type (String) -> Unit
. Il s'agit donc d'une fonction qui accepte une valeur String
et n'a aucune valeur renvoyée. Le paramètre onValueChange
est utilisé comme rappel onValueChange
transmis au composable TextField
.
- Dans la fonction
EditNumberField()
, mettez à jour la fonction composableTextField()
pour qu'elle utilise les paramètres transmis :
TextField(
value = value,
onValueChange = onValueChange,
// Rest of the code
)
- Hissez l'état, déplacez l'état mémorisé de la fonction
EditNumberField()
vers la fonctionTipTimeLayout()
:
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
//...
) {
//...
}
}
- Vous avez hissé l'état vers
TipTimeLayout()
. Transmettez-le maintenant àEditNumberField()
. Dans la fonctionTipTimeLayout()
, mettez à jour l'appel de fonctionEditNumberField
()
pour utiliser l'état hissé :
EditNumberField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
De ce fait, EditNumberField
devient sans état. Vous avez hissé l'état de l'interface utilisateur vers son ancêtre, TipTimeLayout()
. TipTimeLayout()
est désormais le propriétaire de l'état amountInput
.
Formatage basé sur le positionnement
Le formatage basé sur le positionnement permet d'afficher le contenu dynamique sous forme de chaînes. Par exemple, imaginez que vous souhaitiez que la zone de texte Tip amount (Montant du pourboire) affiche une valeur xx.xx
qui peut correspondre à n'importe quel montant calculé et mis en forme dans votre fonction. Pour y parvenir, dans le fichier strings.xml
, vous devez définir la ressource de chaîne avec un argument d'espace réservé, comme dans cet extrait de code :
// No need to copy.
// In the res/values/strings.xml file
<string name="tip_amount">Tip Amount: %s</string>
Dans le code Compose, vous pouvez avoir plusieurs arguments réservés, quel que soit leur type. Un espace réservé de chaîne (string
) est %s
.
Vous pouvez voir que dans le cas du composable de texte dans TipTimeLayout()
, vous transmettez le pourboire mis en forme à la fonction stringResource()
en tant qu'argument.
// No need to copy
Text(
text = stringResource(R.string.tip_amount, "$0.00"),
style = MaterialTheme.typography.displaySmall
)
- Dans la fonction
TipTimeLayout()
, utilisez la propriététip
pour afficher le montant du pourboire. Mettez à jour le paramètretext
du composableText
pour utiliser la variabletip
comme paramètre.
Text(
text = stringResource(R.string.tip_amount, tip),
// ...
Une fois terminées, les fonctions TipTimeLayout()
et EditNumberField()
devraient ressembler à ceci :
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Text(
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
value = value,
onValueChange = onValueChange,
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}
Pour résumer, vous avez hissé l'état amountInput
de EditNumberField()
vers le composable TipTimeLayout()
. Pour que la zone de texte fonctionne comme précédemment, vous devez transmettre deux arguments à la fonction modulable EditNumberField()
: la valeur amountInput
et le rappel lambda qui met à jour la valeur amountInput
à partir de l'entrée utilisateur. Ces modifications vous permettent de calculer le pourboire à partir de la propriété amountInput
dans TipTimeLayout()
et de l'afficher pour les utilisateurs.
- Exécutez l'application sur l'émulateur ou l'appareil, puis saisissez une valeur dans la zone de texte Bill amount (Montant de la facture). Comme illustré sur cette image, le montant du pourboire, qui correspond à 15 % du montant de la facture, s'affiche :
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-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout state
Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.
Si vous souhaitez voir le code de solution, affichez-le sur GitHub.
12. Conclusion
Félicitations ! Vous avez terminé cet atelier de programmation et savez maintenant comment utiliser l'état dans une application Compose.
Résumé
- Dans une application, l'état correspond à toute valeur susceptible de changer au fil du temps.
- Le champ Composition est une description de l'interface utilisateur créée par Compose lorsqu'il exécute des composables. Les applications Compose appellent des fonctions modulables pour convertir les données en UI.
- La composition initiale est une création de l'UI par Compose lorsque des fonctions modulables sont exécutées pour la première fois.
- La recomposition désigne le processus qui consiste à exécuter à nouveau les mêmes composables pour actualiser l'arborescence lorsque leurs données changent.
- Le hissage d'état est un modèle qui consiste à faire remonter un état vers son appelant pour obtenir un composant sans état.