Calculer le pourboire

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.

ebf5c40d4e12d4c7.png

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.

  1. Ouvrez le projet Tip Time dans l'Android Studio.
  2. Si la fenêtre Projet ne s'affiche pas, sélectionnez l'onglet Project (Projet) sur le côté gauche d'Android Studio.
  3. Si elle n'est pas déjà sélectionnée, choisissez la vue Android dans le menu déroulant.

2a83e2b0aee106dd.png

  • 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 Android
  • strings.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

  1. Ouvrez le fichier build.gradle de l'application (Scripts Gradle > build.gradle (Module : Tip_Time.app)).
  2. Dans la section android, ajoutez les lignes suivantes :
buildFeatures {
    viewBinding = true
}
  1. 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).
  2. Appuyez sur Synchroniser.

349d99c67c2f40f1.png

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.

674d243aa6f85b8b.png

  1. Ouvrez MainActivity.kt (app > java > com.example.tiptime > MainActivity).
  2. Remplacez tout le code existant de la classe MainActivity par le code suivant afin de configurer l'élément MainActivity 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)
    }
}
  1. 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.

  1. Cette ligne initialise l'objet binding que vous utiliserez pour accéder à Views dans la mise en page activity_main.xml.
binding = ActivityMainBinding.inflate(layoutInflater)
  1. 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.

  1. Dans MainActivity.kt, dans onCreate(), après l'appel à setContentView(), définissez un écouteur de clics sur le bouton Calculate (Calculer) et demandez-lui d'appeler calculateTip().
binding.calculateButton.setOnClickListener{ calculateTip() }
  1. Toujours dans la classe MainActivity, mais en dehors de onCreate(), ajoutez une méthode d'assistance appelée calculateTip().
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().

  1. 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ément EditText du champ Cost of Service (Coût du service), puis affectez-le à une variable appelée stringInTextField. N'oubliez pas que vous pouvez accéder à l'élément d'interface utilisateur à l'aide de l'objet binding 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.

  1. Convertissez ensuite le texte en nombre décimal. Appelez l'élément toDouble() au niveau de stringInTextField, puis stockez-le dans une variable nommée cost.
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().

  1. Appelez toString() au niveau de binding.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.

  1. Dans calculateTip(), récupérez l'attribut checkedRadioButtonId du RadioGroup tipOptions, puis affectez-le à une variable appelée selectedId.
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.

  1. 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.

  1. Dans calculateTip(), après l'autre code que vous avez ajouté, multipliez tipPercentage par cost, puis affectez-le à une variable appelée tip.
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é".

  1. Attribuez l'attribut isChecked du bouton d'arrondi à la variable roundUp.
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().

32c29f73a3f20f93.png

Si vous souhaitez utiliser plusieurs fonctions mathématiques, il est plus facile d'ajouter une instruction import.

  1. Ajoutez une instruction if qui attribuera le plafond du pourboire à la variable tip si la variable roundUp 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.

  1. Dans calculateTip(), après votre autre code, appelez NumberFormat.getCurrencyInstance().
NumberFormat.getCurrencyInstance()

Vous disposez ainsi d'un outil qui vous permettra de mettre en forme des nombres en tant que devise.

  1. À l'aide de l'outil de mise en forme de nombres, associez un appel à la méthode format() avec tip, puis attribuez le résultat à une variable appelée formattedTip.
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
  1. Notez que NumberFormat s'affiche en rouge, car Android Studio ne peut pas déterminer automatiquement la version de NumberFormat que vous voulez utiliser.
  2. Pointez le curseur sur NumberFormat, puis sélectionnez Import (Importer) dans la fenêtre pop-up qui s'affiche. d9d2f92d5ef01df6.png
  3. Dans la liste des importations possibles, sélectionnez NumberFormat (java.text). Android Studio ajoute une instruction import en haut du fichier MainActivity, et NumberFormat 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.

  1. Ouvrez strings.xml (app > res > values > string.xml).
  2. Modifiez la chaîne tip_amount en remplaçant Tip Amount par Tip Amount: %s.
<string name="tip_amount">Tip Amount: %s</string>

La devise mise en forme sera insérée dans %s.

  1. Définissez maintenant le texte de tipResult. Revenez à la méthode calculateTip() dans MainActivity.kt, appelez getString(R.string.tip_amount, formattedTip) et affectez-le à l'attribut text de l'élément TextView 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.

  1. Ouvrez activity_main.xml (app > res > layouts > activity_main.xml).
  2. Recherchez l'élément TextView tip_result.
  3. Supprimez la ligne comportant l'attribut android:text.
android:text="@string/tip_amount"
  1. Ajoutez une ligne pour l'attribut tools:text défini sur Tip 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.

  1. Notez que le texte des outils apparaît dans l'éditeur de mise en page.
  2. Exécutez votre application. Saisissez le montant du coût et sélectionnez quelques options, puis appuyez sur le bouton Calculate (Calculer).

42fd6cd5e24ca433.png

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 ?

  1. 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").
  2. 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).
  3. 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.

  1. Appuyez sur le bouton Logcat en bas d'Android Studio ou sélectionnez Affichage > Outils Windows > Logcat dans les menus.

1b68ee5190018c8a.png

  1. La fenêtre Logcat s'affiche en bas d'Android Studio, avec un texte étrange. 22139575476ae9d.png

Le texte est ce que l'on appelle une trace de la pile, qui répertorie les méthodes appelées lors du plantage.

  1. 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)
  1. 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.

  1. Poursuivez la lecture. Vous verrez quelques appels à parseDouble().
  2. Sous ces appels, recherchez la ligne contenant calculateTip. Notez qu'elle inclut également la classe MainActivity.
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
  1. 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 convertit String en Double et attribue le résultat à la variable cost.
val cost = stringInTextField.toDouble()
  1. Dans la documentation Kotlin, recherchez la méthode toDouble() qui fonctionne au niveau d'un élément String. Cette méthode est appelée String.toDouble().
  2. 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.

  1. Dans calculateTip(), modifiez la ligne qui déclare la variable cost pour appeler toDoubleOrNull() au lieu d'appeler toDouble().
val cost = stringInTextField.toDoubleOrNull()
  1. Après cette ligne, ajoutez une instruction pour vérifier si cost est null et, le cas échéant, pour renvoyer la méthode. L'instruction return 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 instruction return avec une expression.
if (cost == null) {
    return
}
  1. Exécutez à nouveau votre application.
  2. 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 :

  1. saisit un montant valide pour le coût ;
  2. appuie sur Calculer pour calculer le pourboire ;
  3. supprime le coût ;
  4. 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.

  1. 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.
  2. Dans le if que vous venez d'ajouter, ajoutez une ligne avant l'instruction return pour définir l'attribut text de tipResult 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é.

  1. 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.

  1. Ouvrez MainActivity.kt (app > java > com.example.tiptime > MainActivity).
  2. 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.

3737ebab72be9a5b.png

  1. 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. 6205e927b4c14cf3.png

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é.

  1. Sélectionnez Rendre "calculateTip" "privé" ou ajoutez le mot clé private avant fun calculateTip(). La ligne grise sous calculateTip() 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.

  1. 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. 1d2c6f8415e96231.png
  2. Sélectionnez l'option qui commence par File (Fichier), puis appuyez sur OK. L'inspection sera ainsi limitée à MainActivity.kt.
  3. Une fenêtre contenant les résultats de l'inspection s'affiche au bas de la page.
  4. 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". e40a6876f939c0d9.png
  5. 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 variable binding. 8d9d7b5fc7ac5332.png
  6. Appuyez sur le bouton Rendre la "liaison" "privée". Android Studio supprime le problème des résultats d'inspection.
  7. 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
  1. 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. 781017cbcada1194.png
  2. En examinant le code, vous constaterez que selectedId n'est utilisé que deux fois : d'abord dans la ligne mise en surbrillance où la valeur tipOptions.checkedRadioButtonId lui est attribuée, et dans la ligne suivante après when.
  3. Appuyez sur le bouton Variable intégrée. Android Studio remplace selectedId dans l'expression when 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.

  1. Copiez l'expression à droite de = sur la ligne où roundUp est attribué.
val roundUp = binding.roundUpSwitch.isChecked
  1. 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)
}
  1. 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.

  1. Identifiez le code en double dans MainActivity.kt. Ces lignes de code peuvent être utilisées plusieurs fois dans la fonction calculateTip(), une fois pour 0.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)
  1. 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)
}
  1. Mettez à jour la fonction calculateTip() pour qu'elle utilise la fonction d'assistance displayTip() et qu'elle recherche également 0.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.

966018df4a149822.png

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 groupe RadioGroup pour identifier l'élément RadioButton 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

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.