1. Avant de commencer
Dans cet atelier de programmation, vous écrirez le code de la calculatrice de pourboire qui sera intégrée dans l'interface utilisateur que vous avez créée lors de l'atelier de programmation précédent, intitulé "Créer des mises en page XML pour Android".
Conditions préalables
- Vous disposez du code provenant de l'atelier de programmation Créer des mises en page XML pour Android.
- Vous savez exécuter une application Android depuis Android Studio dans l'émulateur ou sur un appareil.
Points abordés
- Structure de base des applications Android
- Lire les valeurs de l'interface utilisateur dans le code et les manipuler
- Utiliser une liaison de vue au lieu de
findViewById()
pour écrire plus facilement le code qui interagit avec les vues - Utiliser des nombres décimaux dans Kotlin avec le type de données
Double
- Mettre en forme des nombres en tant que devises
- Utiliser des paramètres "string" pour créer des chaînes de façon dynamique
- Utiliser Logcat dans Android Studio pour détecter les problèmes dans l'application
Objectifs de l'atelier
- Création d'une application de calcul avec un bouton Calculer fonctionnel
Ce dont vous avez besoin
- Un ordinateur exécutant la dernière version stable d'Android Studio
- Code de démarrage de l'application Tip Time contenant la mise en page d'une calculatrice de pourboire
2. Présentation de l'application de démarrage
L'application Tip Time issue du dernier atelier de programmation contient l'interface utilisateur nécessaire pour afficher une calculatrice de pourboire, mais le code permettant de calculer le pourboire n'a pas encore été créé. Un bouton Calculer s'affiche, mais il ne fonctionne pas encore. L'élément EditText
du champ Cost of Service (Coût du service) permet à l'utilisateur de saisir le coût du service. Une liste de RadioButtons
lui permet aussi de sélectionner le pourcentage de pourboire, tandis qu'un bouton (Switch
) lui permet de décider si le pourboire doit être arrondi ou non. Le montant du pourboire s'affiche dans une vue TextView
. Enfin, l'élément Button
correspondant à l'option Calculate (Calculer) indique à l'application de récupérer les données des autres champs et de calculer le montant du pourboire. C'est là que cet atelier de programmation intervient.
Structure du projet d'application
Dans votre IDE, un projet d'application contient un certain nombre d'éléments, y compris du code Kotlin, des mises en page XML et d'autres ressources telles que des chaînes et des images. Avant de modifier l'application, nous vous conseillons de vous familiariser avec son fonctionnement.
- Ouvrez le projet Tip Time dans l'Android Studio.
- Si la fenêtre Projet ne s'affiche pas, sélectionnez l'onglet Project (Projet) sur le côté gauche d'Android Studio.
- Si elle n'est pas déjà sélectionnée, choisissez la vue Android dans le menu déroulant.
- Dossier java pour les fichiers Kotlin (ou les fichiers Java)
MainActivity
: classe dans laquelle sera placé tout le code Kotlin correspondant à la logique de la calculatrice de pourboire- Dossier res contenant les ressources de l'application
activity_main.xml
: fichier de mise en page destiné à l'application Androidstrings.xml
: contient les ressources de chaîne de l'application Android- Dossier Scripts Gradle
Gradle est le système de compilation automatisé utilisé par Android Studio. Chaque fois que vous modifiez du code, ajoutez une ressource ou apportez d'autres modifications à votre application, Gradle identifie les changements et prend les mesures nécessaires pour recompiler l'application. Il installe également l'application dans l'émulateur ou sur l'appareil physique et contrôle son exécution.
D'autres dossiers et fichiers sont utilisés pour compiler votre application, mais ceux-ci sont les principaux que vous utiliserez dans cet atelier de programmation et dans les suivants.
3. Liaison de vue
Afin de calculer le pourboire, le code doit avoir accès à tous les éléments de l'interface utilisateur pour lire l'entrée de l'utilisateur. Comme nous l'avons vu précédemment, le code doit identifier une référence à un objet View
tel que Button
ou TextView
pour pouvoir appeler des méthodes au niveau de View
ou pour pouvoir accéder à ses attributs. Le framework Android fournit une méthode, findViewById()
, qui répond exactement à vos besoins : lorsqu'elle reçoit l'ID d'un élément View
, elle renvoie une référence à celui-ci. Cette approche fonctionne, mais lorsque vous ajoutez de nouvelles vues à votre application et que l'interface utilisateur gagne en complexité, l'utilisation de findViewById()
peut se révéler fastidieuse.
Pour plus de commodité, Android propose également une fonctionnalité appelée la liaison de vue. Avec quelques efforts en amont, la liaison de vue permet d'appeler beaucoup plus facilement et rapidement des méthodes au niveau des vues de l'interface utilisateur. Vous devez activer la liaison de vue pour votre application dans Gradle et apporter des modifications au code.
Activer la liaison de vue
- Ouvrez le fichier
build.gradle
de l'application (Scripts Gradle > build.gradle (Module : Tip_Time.app)). - Dans la section
android
, ajoutez les lignes suivantes :
buildFeatures { viewBinding = true }
- Prenez note du message qui s'affiche : Gradle files have changed from last project sync (Les fichiers Gradle ont changé depuis la dernière synchronisation du projet).
- Appuyez sur Synchroniser.
Après quelques instants, le message Gradle sync finished (Synchronisation Gradle terminée) s'affiche en bas de la fenêtre Android Studio. Si vous le souhaitez, vous pouvez fermer le fichier build.gradle
.
Initialiser l'objet de liaison
Dans les ateliers de programmation précédents, vous avez vu la méthode onCreate()
dans la classe MainActivity
. Il s'agit de l'une des premières étapes appelées lorsque votre application démarre et que MainActivity
est initialisé. Au lieu d'appeler findViewById()
pour chaque élément View
de votre application, vous ne devez créer et initialiser l'objet de liaison qu'une fois.
- Ouvrez
MainActivity.kt
(app > java > com.example.tiptime > MainActivity). - Remplacez tout le code existant de la classe
MainActivity
par le code suivant afin de configurer l'élémentMainActivity
pour qu'il utilise la liaison de vue :
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
- Cette ligne déclare une variable de niveau supérieur dans la classe de l'objet de liaison. Elle est définie à ce niveau, car elle sera utilisée dans plusieurs méthodes de la classe
MainActivity
.
lateinit var binding: ActivityMainBinding
Le mot clé lateinit
est nouveau. C'est la promesse que votre code initialise la variable avant de l'utiliser. Sans cela, votre application plantera.
- Cette ligne initialise l'objet
binding
que vous utiliserez pour accéder àViews
dans la mise en pageactivity_main.xml
.
binding = ActivityMainBinding.inflate(layoutInflater)
- Définissez la vue du contenu de l'activité. Au lieu de transmettre l'ID de ressource de la mise en page (
R.layout.activity_main
), vous devez spécifier la racine de la hiérarchie des vues de votre application,binding.root
.
setContentView(binding.root)
Vous vous rappelez peut-être le concept de vues parent et de vues enfant. La racine se connecte à toutes ces vues.
Lorsque vous avez besoin d'une référence à un élément View
dans votre application, vous pouvez l'obtenir à partir de l'objet binding
au lieu d'appeler findViewById()
. L'objet binding
définit automatiquement les références à chaque élément View
associé à un ID dans votre application. La liaison de vue est tellement concise que, dans la plupart des cas, vous n'aurez même pas besoin de créer une variable pour conserver la référence d'un élément View
. Il vous suffit de l'utiliser directement depuis l'objet de liaison.
// Old way with findViewById()
val myButton: Button = findViewById(R.id.my_button)
myButton.text = "A button"
// Better way with view binding
val myButton: Button = binding.myButton
myButton.text = "A button"
// Best way with view binding and no extra variable
binding.myButton.text = "A button"
Pratique, non ?
4. Calculer le pourboire
Pour calculer le pourboire, l'utilisateur doit d'abord appuyer sur le bouton Calculer. Cette action implique la vérification de l'interface utilisateur afin de déterminer le coût du service et le pourboire que l'utilisateur souhaite laisser. À l'aide de ces informations, le montant total des frais de service sera calculé, et le montant du pourboire s'affichera.
Ajouter un écouteur de clics au bouton
La première étape consiste à ajouter un écouteur de clics pour spécifier l'action du bouton Calculate (Calculer) lorsque l'utilisateur appuie dessus.
- Dans
MainActivity.kt
, dansonCreate()
, après l'appel àsetContentView()
, définissez un écouteur de clics sur le bouton Calculate (Calculer) et demandez-lui d'appelercalculateTip()
.
binding.calculateButton.setOnClickListener{ calculateTip() }
- Toujours dans la classe
MainActivity
, mais en dehors deonCreate()
, ajoutez une méthode d'assistance appeléecalculateTip()
.
fun calculateTip() {
}
C'est là que vous ajouterez le code pour vérifier l'interface utilisateur et calculer le pourboire.
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.calculateButton.setOnClickListener{ calculateTip() }
}
fun calculateTip() {
}
}
Récupérer le coût du service
Pour calculer le pourboire, vous devez d'abord indiquer le coût du service. Le texte est stocké dans EditText
, mais il doit être sous forme de nombre pour pouvoir l'utiliser dans les calculs. Vous vous souvenez peut-être du type Int
utilisé dans d'autres ateliers de programmation, mais Int
ne peut contenir que des entiers. Pour utiliser un nombre décimal dans votre application, utilisez le type de données Double
au lieu de Int
. Pour en savoir plus sur les types de données numériques dans Kotlin, consultez la documentation. Kotlin fournit une méthode permettant de convertir une valeur String
en Double
, appelée toDouble()
.
- Tout d'abord, récupérez le texte indiquant le coût du service. Dans la méthode
calculateTip()
, récupérez l'attribut texte de l'élémentEditText
du champ Cost of Service (Coût du service), puis affectez-le à une variable appeléestringInTextField
. N'oubliez pas que vous pouvez accéder à l'élément d'interface utilisateur à l'aide de l'objetbinding
et qu'il peut être référencé en fonction du nom de son ID de ressource en casse mixte.
val stringInTextField = binding.costOfService.text
.text
apparaît à la fin. La première partie, binding.costOfService
, fait référence à l'élément d'interface utilisateur correspondant au coût du service. Ajouter .text
à la fin indique d'utiliser ce résultat (un objet EditText
) pour obtenir la propriété text
. Ce procédé, appelé chaînage, est très courant dans Kotlin.
- Convertissez ensuite le texte en nombre décimal. Appelez l'élément
toDouble()
au niveau destringInTextField
, puis stockez-le dans une variable nomméecost
.
val cost = stringInTextField.toDouble()
Toutefois, cela ne fonctionne pas : toDouble()
doit être appelé au niveau d'un objet String
. Il s'avère que l'attribut text
d'un élément EditText
est Editable
, car il représente du texte qui peut être modifié. Heureusement, vous pouvez convertir un élément Editable
en chaîne (String
) en appelant toString()
.
- Appelez
toString()
au niveau debinding.costOfService.text
pour le convertir en chaîne (String
) :
val stringInTextField = binding.costOfService.text.toString()
Maintenant, stringInTextField.toDouble()
fonctionne.
À ce stade, la méthode calculateTip()
devrait se présenter comme suit :
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
}
Obtenir le pourcentage de pourboire
Jusqu'à présent, vous avez le coût du service. Vous avez maintenant besoin du pourcentage de pourboire que l'utilisateur a sélectionné dans un groupe (RadioGroup
) de RadioButtons
.
- Dans
calculateTip()
, récupérez l'attributcheckedRadioButtonId
duRadioGroup
tipOptions
, puis affectez-le à une variable appeléeselectedId
.
val selectedId = binding.tipOptions.checkedRadioButtonId
Vous savez maintenant quel RadioButton
a été sélectionné (R.id.option_twenty_percent
, R.id.option_eighteen_percent
ou R.id.fifteen_percent
), mais vous avez besoin du pourcentage correspondant. Vous pouvez écrire une série d'instructions if/else
, mais il est beaucoup plus simple d'utiliser une expression when
.
- Ajoutez les lignes suivantes pour obtenir le pourcentage de pourboire.
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
À ce stade, la méthode calculateTip()
devrait se présenter comme suit :
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
}
Calculer le pourboire et l'arrondir à sa valeur supérieure
Maintenant que vous connaissez le coût du service et le pourcentage du pourboire, le calcul du pourboire est très simple : il correspond au coût multiplié par le pourcentage de pourboire (pourboire = coût du service * pourcentage de pourboire). Cette valeur peut être arrondie.
- Dans
calculateTip()
, après l'autre code que vous avez ajouté, multiplieztipPercentage
parcost
, puis affectez-le à une variable appeléetip
.
var tip = tipPercentage * cost
Notez l'utilisation de var
au lieu de val
. Cela est dû au fait que vous devrez peut-être arrondir la valeur si l'utilisateur a sélectionné cette option. Celle-ci peut donc changer.
Pour un élément Switch
, vous pouvez vérifier l'attribut isChecked
pour voir si le bouton est défini sur "activé".
- Attribuez l'attribut
isChecked
du bouton d'arrondi à la variableroundUp
.
val roundUp = binding.roundUpSwitch.isChecked
Le terme "arrondi" consiste à augmenter ou diminuer un nombre décimal à la valeur entière la plus proche. Toutefois, dans ce cas, vous devez seulement arrondir à la valeur supérieure. Pour ce faire, utilisez la fonction ceil()
. Plusieurs fonctions portent ce nom, mais celle que vous souhaitez utiliser est définie dans kotlin.math
. Vous pouvez ajouter une instruction import
, mais dans ce cas, il est plus simple d'indiquer simplement à Android Studio ce que vous voulez à l'aide de kotlin.math.ceil()
.
Si vous souhaitez utiliser plusieurs fonctions mathématiques, il est plus facile d'ajouter une instruction import
.
- Ajoutez une instruction
if
qui attribuera le plafond du pourboire à la variabletip
si la variableroundUp
est "true".
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
À ce stade, la méthode calculateTip()
devrait se présenter comme suit :
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
}
Mettre en forme le pourboire
Votre application est maintenant à deux doigts d'être fonctionnelle. Maintenant que vous avez calculé le pourboire, il vous suffit de le mettre en forme et de l'afficher.
Comme vous pouvez l'imaginer, Kotlin propose différentes méthodes pour mettre en forme de différents types de nombres. Toutefois, le montant du pourboire diffère légèrement. Il représente une valeur monétaire. Chaque pays emploie sa propre devise et appliquent des règles distincte pour la mise en forme des nombres décimaux. Par exemple, en dollars américains, 1234,56 serait converti en 1,234.56 $, mais en euros, le résultat serait de 1 234,56 €. Heureusement, Android propose des méthodes de mise en forme des nombres en tant que devises. Vous n'avez donc pas besoin de connaître toutes les possibilités. Le système met automatiquement en forme la devise en fonction de la langue et d'autres paramètres choisis par l'utilisateur sur son téléphone. Pour en savoir plus sur NumberFormat, consultez la documentation destinée aux développeurs Android.
- Dans
calculateTip()
, après votre autre code, appelezNumberFormat.getCurrencyInstance()
.
NumberFormat.getCurrencyInstance()
Vous disposez ainsi d'un outil qui vous permettra de mettre en forme des nombres en tant que devise.
- À l'aide de l'outil de mise en forme de nombres, associez un appel à la méthode
format()
avectip
, puis attribuez le résultat à une variable appeléeformattedTip
.
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
- Notez que
NumberFormat
s'affiche en rouge, car Android Studio ne peut pas déterminer automatiquement la version deNumberFormat
que vous voulez utiliser. - Pointez le curseur sur
NumberFormat
, puis sélectionnez Import (Importer) dans la fenêtre pop-up qui s'affiche. - Dans la liste des importations possibles, sélectionnez NumberFormat (java.text). Android Studio ajoute une instruction
import
en haut du fichierMainActivity
, etNumberFormat
cesse de s'afficher en rouge.
Afficher le pourboire
L'heure est venue d'afficher le pourboire dans l'élément TextView
du montant du pourboire de votre application. Vous pouvez simplement assigner formattedTip
à l'attribut text
, mais nous vous conseillons d'ajouter un libellé expliquant ce que ce montant représente. Aux États-Unis et en anglais, il peut s'afficher sous la forme Montant du pourboire : 12.34 $, mais dans d'autres langues, le nombre doit apparaître au début, voire au milieu de la chaîne. Le framework Android fournit un mécanisme appelé paramètres de chaîne, de sorte qu'une personne traduisant votre application puisse modifier la position du nombre si nécessaire.
- Ouvrez
strings.xml
(app > res > values > string.xml). - Modifiez la chaîne
tip_amount
en remplaçantTip Amount
parTip Amount: %s
.
<string name="tip_amount">Tip Amount: %s</string>
La devise mise en forme sera insérée dans %s
.
- Définissez maintenant le texte de
tipResult
. Revenez à la méthodecalculateTip()
dansMainActivity.kt
, appelezgetString(R.string.tip_amount, formattedTip)
et affectez-le à l'attributtext
de l'élémentTextView
du résultat de pourboire.
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
À ce stade, la méthode calculateTip()
devrait se présenter comme suit :
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
Vous avez presque terminé. Lorsque vous développez votre application (et que vous en affichez l'aperçu), il est utile de disposer d'un espace réservé pour TextView
.
- Ouvrez
activity_main.xml
(app > res > layouts > activity_main.xml). - Recherchez l'élément
TextView
tip_result
. - Supprimez la ligne comportant l'attribut
android:text
.
android:text="@string/tip_amount"
- Ajoutez une ligne pour l'attribut
tools:text
défini surTip Amount: $10
.
tools:text="Tip Amount: $10"
Comme il s'agit d'un espace réservé, vous n'avez pas besoin d'extraire la chaîne dans une ressource. Il ne s'affiche pas lorsque vous exécutez l'application.
- Notez que le texte des outils apparaît dans l'éditeur de mise en page.
- Exécutez votre application. Saisissez le montant du coût et sélectionnez quelques options, puis appuyez sur le bouton Calculate (Calculer).
Félicitations ! Ça fonctionne ! Si vous n'obtenez pas le montant correct du pourboire, revenez à l'étape 1 de cette section et assurez-vous d'avoir apporté toutes les modifications de code nécessaires.
5. Tester et déboguer
Vous avez exécuté l'application à différentes étapes pour vous assurer qu'elle répond à vos attentes, mais vous devez maintenant procéder à des tests supplémentaires.
Pour l'instant, réfléchissez à la façon dont les informations transitent par votre application dans la méthode calculateTip()
, ainsi qu'aux problèmes éventuels à chaque étape.
Par exemple, que se passerait-il sur cette ligne :
val cost = stringInTextField.toDouble()
si stringInTextField
ne représentait pas un nombre ? Que se passerait-il si l'utilisateur ne saisissait aucun texte et que stringInTextField
était vide ?
- Exécutez l'application dans l'émulateur, mais au lieu d'utiliser Run > Run ‘app (Exécuter > Exécuter "application"), utilisez Run > Debug ‘app (Exécuter > Déboguer "application").
- Essayez différentes combinaisons de coût, montant du pourboire et pourboire arrondi ou non, puis vérifiez que vous obtenez le résultat attendu pour chaque cas lorsque vous appuyez sur Calculate (Calculer).
- Essayez maintenant de supprimer tout le texte du champ Coût du service, puis appuyez sur Calculer. Le programme plante.
Déboguer le plantage
La première étape pour résoudre un bug consiste à comprendre ce qui s'est passé. Android Studio conserve un journal des événements dans le système, que vous pouvez utiliser pour identifier le problème.
- Appuyez sur le bouton Logcat en bas d'Android Studio ou sélectionnez Affichage > Outils Windows > Logcat dans les menus.
- La fenêtre Logcat s'affiche en bas d'Android Studio, avec un texte étrange.
Le texte est ce que l'on appelle une trace de la pile, qui répertorie les méthodes appelées lors du plantage.
- Faites défiler le texte Logcat vers le haut jusqu'à ce que vous trouviez une ligne contenant le texte
FATAL EXCEPTION
.
2020-06-24 10:09:41.564 24423-24423/com.example.tiptime E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tiptime, PID: 24423
java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
at com.example.tiptime.MainActivity$onCreate$1.onClick(MainActivity.kt:17)
- Faites défiler la page vers le bas jusqu'à la ligne contenant
NumberFormatException
.
java.lang.NumberFormatException: empty String
À droite, empty String
apparaît. Le type d'exception indique qu'il s'agissait d'une erreur liée au format numérique. Le reste indique la base du problème : un élément String
vide a été détecté alors qu'il aurait dû contenir une valeur.
- Poursuivez la lecture. Vous verrez quelques appels à
parseDouble()
. - Sous ces appels, recherchez la ligne contenant
calculateTip
. Notez qu'elle inclut également la classeMainActivity
.
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
- Examinez attentivement cette ligne. Vous verrez exactement où l'appel a été effectué (ligne 22) dans
MainActivity.kt
. Notez que si vous avez saisi votre code différemment, il peut s'agir d'un nombre différent. Cette ligne convertitString
enDouble
et attribue le résultat à la variablecost
.
val cost = stringInTextField.toDouble()
- Dans la documentation Kotlin, recherchez la méthode
toDouble()
qui fonctionne au niveau d'un élémentString
. Cette méthode est appeléeString.toDouble()
. - La page indique "Exceptions :
NumberFormatException
" si la chaîne n'est pas une représentation valide d'un nombre.
Une exception désigne la façon dont le système signale un problème. Dans ce cas, le problème est que toDouble()
n'a pas réussi à convertir l'élément String
vide en Double
. Même si EditText
contient inputType=numberDecimal
, il est toujours possible de saisir des valeurs que toDouble()
ne parvient pas à gérer, comme une chaîne vide.
En savoir plus sur l'attribut "null"
L'appel de toDouble()
au niveau d'une chaîne vide ou une chaîne qui ne représente pas un nombre décimal valide ne fonctionne pas. Heureusement, Kotlin fournit également une méthode appelée toDoubleOrNull()
qui gère ces problèmes. Elle renvoie un nombre décimal si cela est possible. Dans le cas contraire, elle renvoie null
en cas de problème.
Null est une valeur spéciale qui signifie "aucune valeur". Elle diffère de Double
ayant la valeur 0.0
ou d'un élément String
vide sans caractères, ""
. Null
signifie qu'il n'y a aucune valeur, aucun Double
ni aucun élément String
. De nombreuses méthodes attendent une valeur et ne savent pas toujours comment gérer null
. Elles s'arrêtent donc, et l'application plante. C'est pourquoi Kotlin essaie de limiter les cas de figure où null
est utilisé. Vous vous familiariserez davantage avec ce sujet dans les prochaines leçons.
Votre application peut vérifier si la valeur null
est renvoyée par toDoubleOrNull()
, et agit différemment dans ce cas afin de ne pas planter.
- Dans
calculateTip()
, modifiez la ligne qui déclare la variablecost
pour appelertoDoubleOrNull()
au lieu d'appelertoDouble()
.
val cost = stringInTextField.toDoubleOrNull()
- Après cette ligne, ajoutez une instruction pour vérifier si
cost
estnull
et, le cas échéant, pour renvoyer la méthode. L'instructionreturn
signifie que vous devez quitter la méthode sans exécuter le reste des instructions. Si la méthode nécessite de renvoyer une valeur, vous devez la spécifier à l'aide d'une instructionreturn
avec une expression.
if (cost == null) {
return
}
- Exécutez à nouveau votre application.
- Sans texte dans le champ Coût du service, appuyez sur Calculer. Pas de plantage de l'application cette fois ! Félicitations ! Vous avez détecté le bug et, surtout, vous l'avez corrigé.
Gérer un autre cas de figure
Tous les bugs ne provoquent pas le plantage de l'application. Les résultats peuvent parfois perturber l'utilisateur.
Voici un autre exemple à prendre en compte. Que se passe-t-il si l'utilisateur :
- saisit un montant valide pour le coût ;
- appuie sur Calculer pour calculer le pourboire ;
- supprime le coût ;
- appuie à nouveau sur Calculer.
La première fois, le pourboire sera calculé et affiché comme prévu. La seconde fois, la méthode calculateTip()
sera renvoyée rapidement en raison de la vérification que vous venez d'ajouter, mais l'application continuera à afficher le montant du pourboire précédent. Ce comportement peut perturber l'utilisateur. Par conséquent, ajoutez du code pour effacer le montant du pourboire en cas de problème.
- Confirmez qu'il s'agit bien du problème. Pour ce faire, saisissez un coût valide, appuyez sur Calculate (Calculer), puis supprimez le texte et appuyez de nouveau sur Calculate (Calculer). La première valeur devrait toujours s'afficher.
- Dans le
if
que vous venez d'ajouter, ajoutez une ligne avant l'instructionreturn
pour définir l'attributtext
detipResult
sur une chaîne vide.
if (cost == null) {
binding.tipResult.text = ""
return
}
Le montant du pourboire sera effacé avant que le résultat de calculateTip()
ne soit renvoyé.
- Exécutez à nouveau votre application, puis suivez la procédure ci-dessus. La première valeur de pourboire devrait disparaître lorsque vous appuyez à nouveau sur Calculer.
Félicitations ! Vous avez créé une application de calcul de pourboire fonctionnelle pour Android et vous avez fait face à des cas extrêmes.
6. Adopter les bonnes pratiques de codage
Votre outil de calcul des pourboires fonctionne désormais. Toutefois, pour améliorer légèrement le code et le rendre plus facile à utiliser à l'avenir, veillez à adopter les bonnes pratiques de codage.
- Ouvrez
MainActivity.kt
(app > java > com.example.tiptime > MainActivity). - En observant le début de la méthode
calculateTip()
, vous constaterez peut-être qu'elle est soulignée par une ligne grise ondulée.
- Pointez le curseur sur
calculateTip()
. Un message s'affiche : Function ‘calculateTip' could be private (La fonction "calculateTip" peut être privée) avec la suggestion Make ‘calculateTip' ‘private' (Rendre "calculateTip" "privé") en dessous.
Comme nous l'avons vu précédemment, private
signifie que la méthode ou la variable n'est visible que par le code de cette classe (dans ce cas, la classe MainActivity
). Vous n'avez pas besoin de coder en dehors de MainActivity
pour appeler calculateTip()
. Vous pouvez donc rendre cet élément privé (private
) en toute sécurité.
- Sélectionnez Rendre "calculateTip" "privé" ou ajoutez le mot clé
private
avantfun calculateTip()
. La ligne grise souscalculateTip()
disparaît.
Inspecter le code
La ligne grise est très subtile. Il est donc facile de l'ignorer. Vous pouvez parcourir l'intégralité du fichier pour identifier les autres lignes grises, mais il existe un moyen plus simple de toutes les trouver.
- Après avoir ouvert
MainActivity.kt
, sélectionnez Analyze > Inspect Code (Analyser > Inspecter le code) dans le menu. Une boîte de dialogue intitulée Specify Inspection Scope (Spécifier l'étendue de l'inspection) s'affiche. - Sélectionnez l'option qui commence par File (Fichier), puis appuyez sur OK. L'inspection sera ainsi limitée à
MainActivity.kt
. - Une fenêtre contenant les résultats de l'inspection s'affiche au bas de la page.
- Cliquez sur les triangles gris à côté de Kotlin, puis sur Style issues (Problèmes de style) jusqu'à ce que deux messages s'affichent. Le premier indique que les Les membres de la classe peuvent avoir une visibilité "privée".
- Cliquez sur les triangles gris jusqu'à ce que le message La propriété "binding" pourrait être privée, puis cliquez sur le message. Android Studio affiche une partie du code dans
MainActivity
et met en évidence la variablebinding
. - Appuyez sur le bouton Rendre la "liaison" "privée". Android Studio supprime le problème des résultats d'inspection.
- Si vous examinez
binding
dans le code, vous constaterez qu'Android Studio a ajouté le mot cléprivate
avant la déclaration.
private lateinit var binding: ActivityMainBinding
- Cliquez sur les triangles gris dans les résultats jusqu'à ce que le message La déclaration de variable pourrait être intégrée. Android Studio affiche à nouveau une partie du code, mais cette fois, il met en évidence la variable
selectedId
. - En examinant le code, vous constaterez que
selectedId
n'est utilisé que deux fois : d'abord dans la ligne mise en surbrillance où la valeurtipOptions.checkedRadioButtonId
lui est attribuée, et dans la ligne suivante aprèswhen
. - Appuyez sur le bouton Variable intégrée. Android Studio remplace
selectedId
dans l'expressionwhen
par la valeur attribuée à la ligne précédente. Il supprime ensuite complètement la ligne précédente, car elle n'est plus nécessaire.
val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
Et voilà ! Votre code comporte une ligne de moins et une variable de moins.
Suppression des variables superflues
Android Studio ne propose plus de résultats d'inspection. Toutefois, si vous examinez attentivement le code, vous constaterez un schéma semblable à celui que vous venez de modifier : la variable roundUp
est attribuée sur une ligne, elle est utilisée sur la ligne suivante, puis elle n'apparaît plus.
- Copiez l'expression à droite de
=
sur la ligne oùroundUp
est attribué.
val roundUp = binding.roundUpSwitch.isChecked
- Remplacez
roundUp
à la ligne suivante par l'expression que vous venez de copier,binding.roundUpSwitch.isChecked
.
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
- Supprimez la ligne contenant
roundUp
, car elle n'est plus nécessaire.
Vous avez fait la même chose qu'Android Studio avec la variable selectedId
. Là encore, votre code comporte une ligne de moins et une variable de moins. Il s'agit de petites modifications, mais elles permettent de rendre votre code plus concis et plus lisible.
(Facultatif) Éliminer le code répétitif
Une fois que votre application fonctionne correctement, vous pouvez rechercher d'autres opportunités de nettoyer le code et de le rendre plus concis. Par exemple, lorsque vous ne saisissez aucune valeur pour le coût du service, l'application met à jour tipResult
et renvoie une chaîne vide ""
. Lorsqu'une valeur est définie, utilisez NumberFormat
pour la mettre en forme. Cette fonctionnalité peut être appliquée ailleurs dans l'application, par exemple pour afficher un pourboire de 0.0
au lieu de la chaîne vide.
Pour réduire la duplication de code très similaire, vous pouvez extraire ces deux lignes de code vers leur propre fonction. Cette fonction d'assistance peut utiliser un élément Double
comme pourboire, le mettre en forme et mettre à jour l'élément TextView
tipResult
à l'écran.
- Identifiez le code en double dans
MainActivity.kt
. Ces lignes de code peuvent être utilisées plusieurs fois dans la fonctioncalculateTip()
, une fois pour0.0
et une fois pour le cas général.
val formattedTip = NumberFormat.getCurrencyInstance().format(0.0)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
- Déplacez le code dupliqué vers sa propre fonction. Une modification du code consiste à appliquer un paramètre "tip" pour que le code fonctionne à plusieurs endroits.
private fun displayTip(tip : Double) {
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
- Mettez à jour la fonction
calculateTip()
pour qu'elle utilise la fonction d'assistancedisplayTip()
et qu'elle recherche également0.0
.
MainActivity.kt
private fun calculateTip() {
...
// If the cost is null or 0, then display 0 tip and exit this function early.
if (cost == null || cost == 0.0) {
displayTip(0.0)
return
}
...
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
// Display the formatted tip value on screen
displayTip(tip)
}
Remarque
Bien que l'application fonctionne maintenant, elle n'est pas encore prête pour la production. Vous devez effectuer d'autres tests. Vous devez également la peaufiner visuellement et respecter les consignes Material Design. Vous découvrirez également comment modifier le thème et l'icône des applications dans les ateliers de programmation suivants.
7. Code de solution
Vous trouverez ci-dessous le code de solution de cet atelier de programmation.
MainActivity.kt
Remarque concernant la première ligne : remplacez le nom du package si le vôtre est différent de com.example.tiptime
.
package com.example.tiptime
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.tiptime.databinding.ActivityMainBinding
import java.text.NumberFormat
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.calculateButton.setOnClickListener { calculateTip() }
}
private fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDoubleOrNull()
if (cost == null) {
binding.tipResult.text = ""
return
}
val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
}
Modifiez strings.xml
<string name="tip_amount">Tip Amount: %s</string>
Modifiez activity_main.xml
...
<TextView
android:id="@+id/tip_result"
...
tools:text="Tip Amount: $10" />
...
Modifiez l'élément build.gradle
du module d'application
android {
...
buildFeatures {
viewBinding = true
}
...
}
8. Résumé
- La liaison de vue vous permet d'écrire plus facilement du code qui interagit avec les éléments d'interface utilisateur de votre application.
- Le type de données
Double
en Kotlin peut stocker un nombre décimal. - Utilisez l'attribut
checkedRadioButtonId
d'un groupeRadioGroup
pour identifier l'élémentRadioButton
sélectionné. - Utilisez
NumberFormat.getCurrencyInstance()
pour obtenir un outil de mise en forme afin de convertir les nombres en devise. - Vous pouvez utiliser des paramètres de chaîne tels que
%s
pour créer des chaînes dynamiques qui pourront être traduites facilement dans d'autres langues. - Les tests sont cruciaux.
- Vous pouvez utiliser Logcat dans Android Studio pour résoudre des problèmes tels que le plantage de l'application.
- Une trace de la pile affiche la liste des méthodes appelées. Elle peut être utile si le code génère une exception.
- Les exceptions indiquent un problème inattendu au niveau du code.
Null
signifie "aucune valeur".- Étant donné que le code ne peut pas gérer toutes les valeurs
null
, faites preuve de prudence. - Utilisez Analyser > Inspecter le code pour consulter des suggestions d'amélioration du code.
9. Autres ateliers de programmation pour améliorer l'UI
Félicitations ! Grâce à vous, la calculatrice de pourboire fonctionne. Vous remarquerez que l'on peut toujours aller plus loin et améliorer l'interface utilisateur afin de la rendre encore plus soignée. Si cela vous intéresse, consultez ces ateliers de programmation supplémentaires pour apprendre à modifier le thème et l'icône de l'application, et pour suivre les bonnes pratiques spécifiques aux consignes Material Design pour l'application Tip Time.
10. En savoir plus
- Type de données
Double
en Kotlin - Types de données numériques en Kotlin
- Sécurité null en Kotlin
- Fichier manifeste d'application
- Liaison de vue (
View
) NumberFormat.getCurrencyInstance()
- Paramètres de chaîne
- Tests
- Logcat
- Analyser une trace de la pile
11. Pour s'entraîner
- Avec l'application de conversion des unités de cuisine de l'exercice précédent, ajoutez du code pour que la logique et les calculs convertissent des unités telles que des onces en millilitres, et vice versa.