Créer une application interactive : Dice Roller

1. Avant de commencer

Dans cet atelier de programmation, vous allez créer l'application interactive Dice Roller, qui permet aux utilisateurs d'appuyer sur un composable Button pour lancer un dé. Le résultat du lancer de dé sera affiché avec un composable Image à l'écran.

Vous utiliserez Jetpack Compose avec Kotlin pour créer la mise en page de votre application, puis écrire la logique métier pour gérer le comportement qui se produira lorsque l'utilisateur appuiera sur le composable Button.

Conditions préalables

  • Vous êtes capable de créer et d'exécuter une application Compose de base dans Android Studio.
  • Vous maîtrisez l'utilisation du composable Text dans une application.
  • Vous savez extraire du texte dans une ressource de chaîne pour faciliter la traduction de votre application et la réutilisation de chaînes.
  • Vous disposez de connaissances de base en programmation Kotlin.

Points abordés

  • Ajouter un composable Button à une application Android avec Compose
  • Ajouter un comportement à un composable Button dans une application Android avec Compose
  • Ouvrir et modifier le code Activity d'une application Android

Objectifs de l'atelier

  • Créer une application Android interactive appelée Dice Roller, qui permettra aux utilisateurs de lancer un dé et affichera le résultat du lancer

Ce dont vous avez besoin

  • Un ordinateur sur lequel est installé Android Studio

Voici à quoi ressemblera l'application à la fin de cet atelier de programmation :

3e9a9f44c6c84634.png

2. Établir une référence

Créer un projet

  1. Dans Android Studio, cliquez sur File > New > New Project (Fichier > Nouveau > Nouveau projet).
  2. Dans la boîte de dialogue New Project (Nouveau projet), sélectionnez Empty Activity (Activité vide), puis cliquez sur Next (Suivant).

39373040e14f9c59.png

  1. Dans le champ Name (Nom), saisissez Dice Roller.
  2. Dans le champ Minimum SDK (SDK minimal), sélectionnez un niveau d'API minimal de 24 (Nougat) dans le menu, puis cliquez sur Finish (Terminer).

8fd6db761068ca04.png

3. Créer l'infrastructure de mise en page

Prévisualiser le projet

Pour prévisualiser le projet, procédez comme suit :

  • Cliquez sur Build & Refresh (Compiler et actualiser) dans le volet Split (Diviser) ou Design (Conception).

9f1e18365da2f79c.png

Vous devriez maintenant voir un aperçu dans le volet Design (Conception). S'il semble petit, ne vous inquiétez pas, car il changera lorsque vous modifierez la mise en page.

b5c9dece74200185.png

Restructurer l'exemple de code

Vous devez modifier une partie du code généré pour qu'il ressemble davantage au thème d'une application de lancer de dé.

Comme vous l'avez vu dans la capture d'écran de l'application finale, l'image représente un dé avec un bouton qui permet de le lancer. Vous structurerez les fonctions composables de façon à refléter cette architecture.

Pour restructurer l'exemple de code, procédez comme suit :

  1. Supprimez la fonction GreetingPreview().
  2. Créez une fonction DiceWithButtonAndImage() avec l'annotation @Composable.

Cette fonction composable représente les composants d'interface utilisateur de la mise en page. Elle contient également la logique du bouton cliquable et de l'affichage de l'image.

  1. Supprimez la fonction Greeting(name: String, modifier: Modifier = Modifier).
  2. Créez une fonction DiceRollerApp() avec les annotations @Preview et @Composable.

Comme cette application se compose uniquement d'un bouton et d'une image, considérez cette fonction composable comme l'application elle-même. C'est d'ailleurs pourquoi elle s'appelle DiceRollerApp().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {

}

@Composable
fun DiceWithButtonAndImage() {

}

Comme vous avez supprimé la fonction Greeting(), l'appel de Greeting("Android") dans le corps du lambda DiceRollerTheme() est mis en évidence en rouge. Cela est dû au fait que le compilateur ne trouve plus de référence à cette fonction.

  1. Supprimez tout le code dans l'expression lambda setContent{} qui se trouve dans la méthode onCreate().
  2. Dans le corps de l'expression lambda setContent{}, appelez l'expression lambda DiceRollerTheme{}, puis, dans l'expression lambda DiceRollerTheme{}, appelez la fonction DiceRollerApp().

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
  1. Dans la fonction DiceRollerApp(), appelez la fonction DiceWithButtonAndImage().

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}

Ajouter un modificateur

Compose utilise un objet Modifier. Cet objet représente un ensemble d'éléments qui décorent ou modifient le comportement des éléments d'interface utilisateur Compose. Il vous permet de styliser les éléments d'interface utilisateur des composants de l'application Dice Roller.

Pour ajouter un modificateur, procédez comme suit :

  1. Modifiez la fonction DiceWithButtonAndImage() pour qu'elle accepte un argument modifier de type Modifier et attribuez-lui la valeur par défaut Modifier.

MainActivity.kt

@Composable 
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}

L'extrait de code précédent peut être déroutant. Examinons-le de plus près. La fonction autorise la transmission d'un paramètre modifier. La valeur par défaut du paramètre modifier est un objet Modifier, ce qui explique la portion = Modifier de la signature de la méthode. La valeur par défaut d'un paramètre permet à toute personne qui appellera cette méthode à l'avenir de décider de transmettre une valeur pour ce paramètre. Si cette personne transmet son propre objet Modifier, elle peut personnaliser le comportement et la décoration de l'interface utilisateur. Si elle choisit de ne pas transmettre d'objet Modifier, la valeur de l'objet par défaut, qui est l'objet Modifier brut, sera alors déduite. Vous pouvez appliquer cette pratique à n'importe quel paramètre. Pour en savoir plus sur les arguments par défaut, consultez la section Arguments par défaut.

  1. Maintenant que le composable DiceWithButtonAndImage() possède un paramètre de modificateur, transmettez un modificateur lorsque le composable est appelé. Étant donné que la signature de la méthode pour la fonction DiceWithButtonAndImage() a été modifiée, un objet Modifier avec les décorations souhaitées devrait être transmis lors de son appel. La classe Modifier est responsable de la décoration ou de l'ajout d'un comportement à un composable dans la fonction DiceRollerApp(). Dans ce cas, vous devez ajouter des décorations importantes à l'objet Modifier transmis à la fonction DiceWithButtonAndImage().

Vous vous interrogez peut-être sur l'intérêt de transmettre un argument Modifier en présence d'un argument par défaut. La raison est la suivante : les composables peuvent faire l'objet d'une recomposition, ce qui signifie essentiellement que le bloc de code dans la méthode @Composable s'exécute à nouveau. Si un objet Modifier est créé dans un bloc de code, il peut être recréé, et cette méthode n'est pas efficace. La recomposition sera abordée ultérieurement dans cet atelier.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier)
  1. Enchaînez une méthode fillMaxSize() au niveau de l'objet Modifier afin que la mise en page occupe la totalité de l'écran.

Cette méthode spécifie que les composants doivent occuper l'espace disponible. Plus tôt dans cet atelier, vous avez vu une capture d'écran de l'interface utilisateur finale de l'application Dice Roller. Une caractéristique notable est que le dé et le bouton sont au centre de l'écran. La méthode wrapContentSize() indique que l'espace disponible doit être au moins aussi grand que les composants qu'il contient. Toutefois, comme la méthode fillMaxSize() est utilisée, si les composants de la mise en page sont plus petits que l'espace disponible, un objet Alignment peut être transmis à la méthode wrapContentSize(), qui spécifie la façon dont les composants doivent s'aligner dans l'espace disponible.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
  1. Enchaînez la méthode wrapContentSize() au niveau de l'objet Modifier, puis transmettez Alignment.Center en tant qu'argument pour centrer les composants. Alignment.Center spécifie qu'un composant doit être centré verticalement et horizontalement.

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)

4. Créer une mise en page verticale

Dans Compose, les mises en page verticales sont créées avec la fonction Column().

La fonction Column() est une mise en page composable qui place ses enfants dans une séquence verticale. Dans l'interface prévue de l'application, vous pouvez voir que l'image du dé s'affiche au-dessus du bouton permettant de le lancer :

7d70bb14948e3cc1.png

Pour créer une mise en page verticale, procédez comme suit :

  1. Dans la fonction DiceWithButtonAndImage(), ajoutez une fonction Column().
  1. Transmettez l'argument modifier de la signature de la méthode DiceWithButtonAndImage() à l'argument de modificateur de Column().

L'argument modifier garantit que les composables de la fonction Column() respectent les contraintes appelées sur l'instance modifier.

  1. Transmettez un argument horizontalAlignment à la fonction Column(), puis définissez-le sur la valeur Alignment.CenterHorizontally.

De cette façon, les enfants compris dans la colonne seront centrés sur l'écran de l'appareil par rapport à la largeur.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

5. Ajouter un bouton

  1. Dans le fichier strings.xml, ajoutez une chaîne et définissez-la sur la valeur Roll.

res/values/strings.xml

<string name="roll">Roll</string>
  1. Dans le corps lambda de Column(), ajoutez une fonction Button().
  1. Dans le fichier MainActivity.kt, ajoutez une fonction Text() à l'élément Button() dans le corps lambda de la fonction.
  2. Transmettez l'ID de ressource de la chaîne roll à la fonction stringResource(), puis transmettez le résultat au composable Text.

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}

6. Insérer une image

L'image du dé est un élément essentiel de l'application. Elle affiche le résultat lorsque l'utilisateur appuie sur le bouton "Lancer". Vous ajouterez cette image avec un composable Image, mais vous avez besoin d'une ressource d'image. Vous devrez donc d'abord télécharger les images fournies pour cette application.

Télécharger les images de dés

  1. Ouvrez cette URL pour télécharger un fichier ZIP d'images de dés sur votre ordinateur, puis attendez la fin du téléchargement.

Recherchez le fichier sur votre ordinateur. Il se trouve probablement dans le dossier Téléchargements.

  1. Décompressez le fichier ZIP pour créer un dossier dice_images contenant six fichiers image avec des valeurs de dé comprises entre 1 et 6.

Ajouter les images du dé à votre application

  1. Dans Android Studio, cliquez sur View > Tool Windows > Resource Manager (Afficher > Outils Windows > Gestionnaire de ressources).
  2. Cliquez sur + > Import Drawables (+ > Importer des drawables) pour ouvrir un explorateur de fichiers.

12f17d0b37dd97d2.png

  1. Recherchez et sélectionnez le dossier contenant six images de dés, puis importez-les.

Les images importées se présenteront comme suit :

4f66c8187a2c58e2.png

  1. Cliquez sur Suivant.

688772df9c792264.png

La boîte de dialogue Import drawables (Importer des drawables) s'affiche et indique l'emplacement des fichiers de ressources dans la structure de fichiers.

  1. Cliquez sur Import (Importer) pour confirmer que vous souhaitez importer les six images.

Les images doivent s'afficher dans le volet Resource Manager (Gestionnaire de ressources).

c2f08e5311f9a111.png

Bien joué ! Dans la tâche suivante, vous utiliserez ces images dans votre application.

Ajouter un composable Image

L'image du dé doit apparaître au-dessus du bouton "Lancer". Compose place les composants d'interface utilisateur de manière séquentielle. En d'autres termes, le composable déclaré en premier s'affiche en premier. Par exemple, la première déclaration s'affiche au-dessus ou avant le composable déclaré après. Les éléments d'un composable Column s'affichent au-dessus et en dessous les uns des autres sur l'appareil. Dans cette application, vous utiliserez un élément Column pour empiler des composables verticalement. Par conséquent, le composable déclaré en premier dans la fonction Column() s'affiche avant le composable qui sera déclaré par la suite dans la même fonction Column().

Pour ajouter un composable Image, procédez comme suit :

  1. Dans le corps de la fonction Column(), créez une fonction Image() avant la fonction Button().

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
  1. Transmettez à la fonction Image() un argument painter, puis attribuez-lui une valeur painterResource qui accepte un argument d'ID de ressource drawable. Pour l'instant, transmettez l'ID de ressource suivant : argument R.drawable.dice_1.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. Chaque fois que vous créez une image dans votre application, vous devez fournir une "description de contenu". Les descriptions de contenu jouent un rôle important dans le développement Android. Elles sont associées à leurs composants d'interface utilisateur respectifs pour améliorer l'accessibilité. Pour en savoir plus sur les descriptions de contenu, consultez la section Décrire chaque élément d'interface utilisateur. Vous pouvez transmettre une description de contenu à l'image en tant que paramètre.

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)

Tous les composants d'interface utilisateur nécessaires sont désormais présents. Toutefois, les éléments Button et Image sont un peu trop proches l'un de l'autre.

54b27140071ac2fa.png

  1. Pour résoudre ce problème, ajoutez un composable Spacer entre les éléments Image et Button. Spacer utilise un élément Modifier comme paramètre. Dans ce cas, Image est au-dessus de Button. Il doit donc y avoir un espace vertical entre eux. Par conséquent, la hauteur de Modifier peut être définie pour s'appliquer à Spacer. Essayez de définir une hauteur de 16.dp. En général, les dimensions en dp sont modifiées par incréments de 4.dp.

MainActivity.kt

Spacer(modifier = Modifier.height(16.dp))
  1. Dans le volet Preview (Aperçu), cliquez sur Build & Refresh (Compiler et actualiser).

Une image semblable à celle-ci devrait apparaître :

73eea4c166f7e9d2.png

7. Créer la logique permettant de lancer les dés

Maintenant que tous les composables nécessaires sont présents, vous allez modifier l'application de sorte qu'un clic sur le bouton lance le dé.

Rendre le bouton interactif

  1. Dans la fonction DiceWithButtonAndImage(), avant la fonction Column(), créez une variable result et attribuez-lui la valeur 1.
  2. Examinez le composable Button. Vous remarquerez que le paramètre onClick lui a été transmis ; ce paramètre est défini sur une paire d'accolades dans lesquelles se trouve le commentaire /*TODO*/. Dans ce cas, les accolades représentent ce qu'on appelle une expression lambda, la zone à l'intérieur des accolades correspondant au corps de cette expression. Lorsqu'une fonction est transmise en tant qu'argument, elle peut également être appelée rappel.

MainActivity.kt

Button(onClick = { /*TODO*/ })

Une expression lambda est un littéral de fonction. C'est une fonction comme les autres, mais au lieu d'être déclarée séparément avec le mot clé fun, elle est écrite de manière intégrée et transmise en tant qu'expression. Le composable Button s'attend à ce qu'une fonction soit transmise en tant que paramètre onClick. C'est l'endroit idéal pour utiliser une expression lambda. Dans cette section, vous écrirez le corps de cette expression.

  1. Dans la fonction Button(), supprimez le commentaire /*TODO*/ de la valeur du corps de l'expression lambda du paramètre onClick.
  2. Un lancer de dé est aléatoire. Pour refléter cette caractéristique dans le code, vous devez utiliser la syntaxe appropriée afin de générer un nombre aléatoire. En langage Kotlin, vous pouvez utiliser la méthode random() au niveau d'une plage de nombres. Dans le corps de l'expression lambda onClick, définissez la variable result sur une plage comprise entre 1 et 6, puis appelez la méthode random() au niveau de cette plage. N'oubliez pas qu'en Kotlin, les plages sont désignées par deux points entre le premier chiffre de la plage et le dernier.

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1),
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

Il est désormais possible d'appuyer sur le bouton, mais cette action n'entraînera pas de changement visuel à ce stade, car vous devez encore créer cette fonctionnalité.

Ajouter une condition à l'application de lancer de dé

Dans la section précédente, vous avez créé une variable result et l'avez codée en dur dans une valeur 1. La valeur de la variable result finira par être réinitialisée lorsque l'utilisateur appuiera sur le bouton "Lancer", ce qui devrait déterminer l'image à afficher.

Les composables sont sans état par défaut. En d'autres termes, ils ne contiennent pas de valeur et peuvent être recomposés à tout moment par le système, ce qui entraîne la réinitialisation de la valeur. Cependant, Compose offre un moyen pratique d'éviter cela. Les fonctions composables peuvent stocker un objet en mémoire à l'aide du composable remember.

  1. Convertissez la variable result en composable remember.

Le composable remember nécessite la transmission d'une fonction.

  1. Dans le corps du composable remember, transmettez une fonction mutableStateOf(), puis transmettez-lui un argument 1.

La fonction mutableStateOf() renvoie un objet observable. Vous vous familiariserez avec les objets observables par la suite. Pour l'instant, rappelez-vous juste que lorsque la valeur de la variable result change, une recomposition est déclenchée, la valeur du résultat est reflétée et l'interface utilisateur est actualisée.

MainActivity.kt

var result by remember { mutableStateOf(1) }

Désormais, lorsque l'utilisateur appuie sur le bouton, la variable result est mise à jour avec une valeur aléatoire.

Vous pouvez maintenant utiliser la variable result pour déterminer l'image à afficher.

  1. Sous l'instanciation de la variable result, créez une variable imageResource immuable définie sur une expression when qui accepte une variable result, puis définissez chaque résultat possible sur son drawable.

MainActivity.kt

val imageResource = when (result) {
    1 -> R.drawable.dice_1
    2 -> R.drawable.dice_2
    3 -> R.drawable.dice_3
    4 -> R.drawable.dice_4
    5 -> R.drawable.dice_5
    else -> R.drawable.dice_6
}
  1. Remplacez l'ID transmis au paramètre painterResource du composable Image à partir du drawable R.drawable.dice_1 par la variable imageResource.
  2. Modifiez le paramètre contentDescription du composable Image pour refléter la valeur de la variable result en convertissant la variable result en chaîne avec toString() et en la transmettant en tant que contentDescription.

MainActivity.kt

Image(
   painter = painterResource(imageResource),
   contentDescription = result.toString()
)
  1. Exécutez votre application.

Votre application de lancer de dé devrait maintenant fonctionner correctement.

3e9a9f44c6c84634.png

8. Télécharger le code de solution

Pour télécharger le code de l'atelier de programmation terminé, utilisez la commande Git suivante :

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git

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.

  1. Accédez à la page du dépôt GitHub fournie pour le projet.
  2. Vérifiez que le nom de la branche correspond à celui spécifié dans l'atelier de programmation. Par exemple, dans la capture d'écran suivante, le nom de la branche est main.

1e4c0d2c081a8fd2.png

  1. Sur la page GitHub du projet, cliquez sur le bouton Code pour afficher une fenêtre pop-up.

1debcf330fd04c7b.png

  1. Dans la fenêtre pop-up, cliquez sur le bouton Download ZIP (Télécharger le fichier ZIP) pour enregistrer le projet sur votre ordinateur. Attendez la fin du téléchargement.
  2. Recherchez le fichier sur votre ordinateur (il se trouve probablement dans le dossier Téléchargements).
  3. Double-cliquez sur le fichier ZIP pour le décompresser. Un dossier contenant les fichiers du projet est alors créé.

Ouvrir le projet dans Android Studio

  1. Lancez Android Studio.
  2. Dans la fenêtre Welcome to Android Studio (Bienvenue dans Android Studio), cliquez sur Open (Ouvrir).

d8e9dbdeafe9038a.png

Remarque : Si Android Studio est déjà ouvert, sélectionnez l'option de menu File > Open (Fichier > Ouvrir).

8d1fda7396afe8e5.png

  1. Dans l'explorateur de fichiers, accédez à l'emplacement du dossier du projet décompressé (il se trouve probablement dans le dossier Téléchargements).
  2. Double-cliquez sur le dossier de ce projet.
  3. Attendez qu'Android Studio ouvre le projet.
  4. Cliquez sur le bouton Run (Exécuter) 8de56cba7583251f.png pour compiler et exécuter l'application. Assurez-vous qu'elle fonctionne correctement.

9. Conclusion

Vous avez utilisé Compose pour créer une application interactive de lancer de dé, intitulée Dice Roller, pour Android.

Résumé

  • Définissez des fonctions composables.
  • Créez des mises en page avec des compositions.
  • Créez un bouton avec le composable Button.
  • Importez des ressources drawable.
  • Affichez une image avec le composable Image.
  • Créez une UI interactive avec des composables.
  • Utilisez le composable remember pour stocker en mémoire les objets d'une composition.
  • Actualisez l'UI avec la fonction mutableStateOf() pour créer un objet observable.

En savoir plus