tp4
tp4
tp4
Nerzic
Dept Informatique TP4 - Activités 2023-24
1. Résumé du TP
Une application Android est généralement composée d’« activités ». Une activité Android est
une interface utilisateur, c’est à dire un écran, associé à une classe Kotlin qui gère les actions de
l’utilisateur. La structure de l’interface est définie par un fichier appelé layout. C’est un fichier
XML qui déclare chaque composant de l’interface, textes, boutons, etc. Donc, pour créer une
activité, on définit un layout et une classe Kotlin.
Dans un layout, il y a des composants : textes, boutons, images, etc. Lorsque l’activité est affichée,
chaque composant existe sous forme d’un objet Kotlin, ce qui permet de le manipuler à l’aide de
ses setters et getters, et d’autres méthodes.
Une activité Android peut lancer une autre activité de la même application, ou d’une autre
application. Les activités lancées successivement forment une sorte de pile et seule l’activité du
dessus, la plus récente, est visible et active. Quand une activité se termine, soit spontanément, soit
avec le bouton back ◁, elle est retirée de la pile et c’est l’activité juste en dessous qui redevient
visible, et ainsi de suite jusqu’à revenir au bureau Android.
Les activités sont démarrées à l’aide d’Intent. Ce sont des objets qui spécifient une action et des
paramètres. Parmi les actions, il y a l’affichage de documents. On peut rajouter des informations
dans un intent, ce qui permet de paramétrer une activité.
1
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Cliquez moi (btn 1)"/>
</LinearLayout>
3. Commencez par comprendre ce layout. Notez que chaque vue possède un identifiant.
4. Lancez l’exécution de l’application sur un AVD.
Pour l’instant, il ne se passe rien quand on clique sur le bouton. Ça va changer, mais il y a
beaucoup de choses à comprendre avant.
On va faire un petit changement dans le thème de l’application pour faire apparaître un titre dans
une barre d’outils, mais qui sera vide dans ce TP.
5. Ouvrez le fichier res/values/themes/themes.xml, le premier des deux et remplacez
dans la ligne 3, parent="Theme.Material3.DayNight.NoActionBar" par seulement
parent="Theme.Material3.DayNight". Ça permet d’afficher la action bar en haut de
chaque activité. C’est une zone optionnelle où on trouve des menus, voir le TP6.
Que déclare le mot-clé override (@Override en Java) ? Il indique que la méthode concernée doit
impérativement exister dans la superclasse, donc que c’est véritablement une surcharge. Pour
voir, changez onCreate en onCreation et vous verrez que le mot-clé override sera en erreur. Ça
permet d’éviter des erreurs, en croyant surcharger une méthode, à tort quand on se trompe dans
le nom ou les paramètres.
2
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
4. Relancez l’application sur l’AVD, cherchez le message concernant onCreate dans le LogCat
(filtrez l’affichage pour ne voir que les messages ayant le tag TP4). Vous voyez aussi que
onResume a été appelée.
5. Appuyez sur le bouton rond ⃝ pour revenir au lanceur (bureau Android), puis le bouton
carré pour revenir dans l’application. Cherchez les messages correspondant pour savoir
quelles méthodes ont été appelées à quel moment.
6. Appuyez sur le bouton back ◁, cherchez le message concernant onDestroy. Vous verrez que
onPause a été appelée juste avant.
7. Relancez l’application puis faites pivoter l’écran (portrait → paysage et inverse). Constatez
que le système Android détruit carrément l’activité, puis la re-crée ! C’est à savoir : on
ne peut pas maintenir des variables membres longtemps dans une activité. Elles sont
réinitialisées à chaque mouvement du téléphone ou arrivée d’une autre application. Ces
informations doivent être stockées autrement.
2.3. Manifeste
Toute activité doit être déclarée dans le fichier AndroidManifest.xml, qui est dans la catégorie
manifests du projet.
• Ouvrez ce fichier et étudiez les balises <application> et <activity>. Le nom de la classe
est mis dans un attribut android:name=".MainActivity". Le point devant le nom de la
classe est une abréviation du package.
• Remarquez la balise <intent-filter> et son contenu. Cette balise sert à désigner l’activité
qui la porte comme étant la principale, celle qu’il faut lancer quand on démarre l’application.
Le problème ne se pose pas ici car il n’y a qu’une seule activité.
3
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
Il suffit de faire :
val variable: Type = findViewById(R.id.identifiant)
Si la même vue est utilisée dans plusieurs méthodes, alors il faut en faire une variable membre :
private lateinit var bouton2: Button
Ensuite, on peut utiliser les setters et getters de la classe. En général, ils portent les mêmes noms
que les attributs XML dans le layout. Par exemple pour les TextView, il y a setText("...") et
getText(), qu’on peut abréger par exemple en vue.text = "blabla".
Concernant l’efficacité, il faut savoir que findViewById fait une recherche dans l’arbre des vues.
C’est considéré comme relativement peu rapide.
Il y a un dernier point à savoir, c’est que la méthode findViewById peut ne pas trouver la vue
souhaitée parce que l’identifiant correspond à aucune vue, par exemple s’il est défini dans une
autre interface. À cause de ça, la fonction findViewById peut retourner null. Alors on doit
définir la variable représentant la vue ainsi :
4
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
Le ? après le type Button indique que la variable peut valoir null. Alors à partir de là, Kotlin
scrute tous les appels aux méthodes sur cette variable. Kotlin refuse de compiler ce qui suit :
bouton1.setText("J'existe sinon je plante")
Vous devez prouver qu’au moment où vous les utilisez, la variable n’est pas nulle. Par exemple
avec une conditionnelle :
if (bouton1 != null) {
bouton1.setText("J'existe")
}
Vous pouvez aussi employer un notation particulière pour dire de ne rien faire si la variable est
nulle :
bouton1?.setText("J'existe ou rien ne se passe")
L’expression a?.b retourne le champ b de a si a n’est pas null et retourne null si a l’est.
Il se peut que findViewById trouve une vue ayant un type différent de celui qu’on attend,
parce qu’on s’est trompé d’identifiant. Dans ce cas, ça déclenche une ClassCastException et
l’application s’arrête. Pour éviter ça, on peut utiliser la syntaxe suivante :
var bouton1: Button? = findViewById(R.id.bouton1) as? Button
Le mot-clé objet as? Classe tente de convertir l’objet dans la classe indiquée, et retourne null
si ce n’est pas possible. Ainsi, la variable vaudra null en cas de problème, et on retombe dans le
cas précédent.
Cette dernière situation est beaucoup plus rare grâce à Android Studio. Il contient un outil de
vérification des layouts et des activités pour éviter ces problèmes.
La méthode findViewById n’est quand même pas du tout recommandée pour une application de
qualité. Il faut employer la technique des view bindings, dans le prochain §.
Exercice
Dans la méthode onCreate, ajoutez deux instructions pour :
a. Récupérer l’objet TextView correspondant au message avec findViewById,
b. Changer le message affiché en “bonjour”. Ne tenez pas compte du message d’avertissement
sur les chaînes en dur.
c. Lancez l’application et vérifiez.
d. Commentez les deux lignes rajoutées.
C’est le mécanisme recommandé pour récupérer les vues d’un layout. Il génère automatiquement
une classe spéciale pour chaque layout. Dans cette classe, les variables membres sont les vues qui
possèdent un identifiant dans le layout.
5
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
C’est à dire que chaque vue ayant un identifiant est traduite en une variable membre du même
nom dans cette classe. Et il y a des méthodes supplémentaires, comme getRoot() et inflate().
1. Pour commencer, il faut modifier le fichier build.gradle du dossier app de cette façon (ne
vous trompez pas de fichier, et faites attention à préserver les lignes existantes) :
buildTypes {
...
}
buildFeatures {
viewBinding = true // génération des ViewBindings
}
compileOptions {
...
2. Synchronisez le projet (Sync Now en haut à droite) afin que Android Studio soit à jour.
3. Dans MainActivity, transformez la méthode onCreate ainsi (import à ajouter) :
// interface utilisateur
lateinit var ui: ActivityMainBinding
6
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
setTitle(localClassName)
...
Les view bindings sont le moyen le plus sûr et le plus rapide d’accéder aux vues : impossibilité de
faire une erreur de type et aucun temps perdu dans des recherches dans l’interface. D’autre part,
toutes les vues sont rassemblées au même endroit avec un nommage correspondant aux identifiants
déclarés dans le layout.
Si on change quelque chose dans le layout sans éditer l’activité, ça provoquera une erreur de
compilation. Tandis qu’avec des findViewById, il est à craindre d’oublier des changements qui se
traduiront par des vues absentes à l’exécution.
Enfin, dernier avantage, pour les tests d’intégration, il est facile de mocker ce genre de classe (voir
le TP sur les tests logiciels dans Android).
Comme elle n’a que des avantages, on n’utilisera que cette technique à l’avenir.
2.5.1. Définition d’un écouteur par ajout d’un attribut dans le layout
La manière la plus simple consiste à ajouter un attribut au bouton dans le layout.xml. Cet attribut
donne le nom d’une méthode qui sera à appeler quand il y aura un clic.
1. Faites cette modification en gardant les autres lignes :
<Button
...
android:text="Cliquez moi (btn 1)"
android:onClick="onBouton1Click"/>
7
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
// variable membre
var compteur = 0
Une deuxième manière pour définir un écouteur consiste à créer une classe anonyme avec une
syntaxe un peu particulière en Kotlin, une lambda. C’est une écriture pour définir une méthode
sans lui donner de nom : (paramètres) -> { corps de la méthode }.
1. Dupliquez le bouton dans le layout et, à sa copie, donnez l’identifiant bouton2 et adaptez
son texte. Enlevez-lui l’attribut android:onClick.
2. Rajoutez ceci à la fin de la méthode onCreate :
8
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
1. Dupliquez le bouton bouton2 dans le layout et, au nouveau, donnez-lui l’identifiant bouton3
et adaptez son texte.
2. Rajoutez ceci à la fin de la méthode onCreate :
Cette syntaxe Kotlin est assez complexe. La base est objet1.setChose(object : Classe)
qui signifie de créer un nouvel objet de la classe (ou interface) indiquée et de le fournir au
setter de l’objet1. Ensuite, la classe est suivie d’accolades qui permettent de surcharger des
méthodes : objet1.setChose(object : Classe { méthodes surchargées... }). Ici, il n’y a
que onClick.
Le problème de cette technique est que this ne désigne pas l’activité, mais l’écouteur privé
anonyme. Décommentez la ligne this.compteur += 3 pour constater que ça ne se compile pas.
Il y a une solution, c’est la 3e ligne, avec this@MainActivity pour désigner le bon this.
Cette technique, de l’écouteur privé anonyme est à réserver à des situations particulières. On en
rencontrera une dans le TP6.
La meilleure manière de faire est probablement (avis personnel du rédacteur) avec les références
de méthodes. Cela consiste à définir une méthode pour gérer chaque événement (clic ou autre),
et à définir un écouteur qui est une référence à cette méthode. C’est la technique qui est la plus
modulaire, la plus simple à écrire, et qui permet de faire des tests unitaires.
1. Dupliquez le 3e bouton dans le layout et donnez-lui l’identifiant bouton4 et adaptez son
texte.
2. Rajoutez ceci à la fin de la méthode onCreate :
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// écouteur pour le bouton4, référence de méthode
ui.bouton4.setOnClickListener(this::onBouton4Click)
}
La syntaxe this::nom_de_methode appelée « référence de méthode » fait créer une sorte d’objet
9
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
caché, n’ayant que la méthode attendue par setOnClickListener. Cette méthode sera appelée
lors d’un événement.
3. Définissez la méthode onBouton4Click comme ceci :
La seule exigence, c’est qu’elle ait un paramètre de type View, même s’il ne sert pas.
L’avantage de cette technique sur les autres, c’est que la méthode peut être privée ou pas. La
méthode peut être placée n’importe où dans le source. Il n’y a aucune syntaxe bizarre à part le
paramètre View. Également, la méthode a librement accès aux variables membres de this, ce qui
l’est pas forcément le cas des écouteurs privés anonymes. La modularité de cette technique est
maximale. Elle peut faire l’objet de tests unitaires (voir le dernier TP du module). Il n’y a aucun
coût à l’exécution (pas de recherche dans un layout. . . ). C’est la solution qui a le plus de qualités.
3. Activités multiples
On va maintenant découvrir les interactions entre différentes activités.
1. Création d’une nouvelle activité :
• Créez une nouvelle activité appelée InfosActivity en utilisant le menu contextuel du
projet : cliquez droit sur app, item New, puis sous-item Activity, et enfin Empty Views
Activity (3 niveaux de menu !). Elle aura un layout appelé activity_infos.xml.
• Refaites l’attribution du layout par view bindings (ActivityInfosBinding) :
– Définissez une variable membre lateinit var ui: ActivityInfosBinding
– Remplacez setContentView(R.layout.activity_infos) par :
2. Vérification du manifeste :
• Cette nouvelle activité a été ajoutée dans AndroidManifest.xml.
• Seule l’activité MainActivity est démarrable. Ne mettez pas d’intent filter à la nouvelle.
3. Définition des layouts :
• Recopiez le contenu du layout activity_main.xml dans activity_infos.xml,
• Dans activity_infos.xml
– Changez le titre du message en “Informations”,
– Enlevez tous les boutons sauf le premier,
– Changez le titre du bouton restant en “Retour”,
– Enlevez l’attribut android:click.
10
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
finish()
• On peut donc envisager un lancement suivi d’un finish() dans le cas où une activité veut
lancer une autre et se terminer. Ainsi, le bouton back ne ramènera pas dans la première.
3.2. Exercice
1. On va ajouter encore deux boutons dans activity_main.xml :
a. Ajoutez un bouton bouton5 avec le titre “infos+retour”,
b. Ajoutez un bouton bouton6 avec le titre “infos+fin”,
c. Le premier bouton doit lancer InfosActivity en laissant MainActivity vivante
(ajoutez un écouteur de type référence de méthode en codant ce qu’il faut),
d. Le deuxième bouton doit lancer InfosActivity mais en terminant MainActivity
(idem).
2. Testez les séquences suivantes (ce sont des tests fonctionnels) :
• Test 1
a. Lancez l’application sur un AVD, on doit voir MainActivity
b. Appuyez sur le bouton back ◁. Ça doit ramener sur le bureau Android.
• Test 2
a. Lancez l’application sur un AVD, on doit voir MainActivity
b. Appuyez sur le bouton 5 “infos+retour”, on doit aller sur InfosActivity
c. Appuyez sur le bouton back ◁. On doit revenir dans MainActivity.
d. Appuyez sur le bouton back ◁. Ça doit ramener sur le bureau Android et non pas
dans InfosActivity.
• Test 3
a. Lancez l’application sur un AVD, on doit voir MainActivity
11
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
12
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
Quand il y a le choix entre plusieurs applications, le système affiche un dialogue demandant avec
quelle application continuer, une seule fois ou toujours. . .
13
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
// interface utilisateur
lateinit var ui: ActivityLoginBinding
Il y a deux commentaires marqués TODO. Il faut créer un Intent pour lancer MainActivity, mais
en ajoutant ce qu’on appelle des extras, c’est à dire des valeurs associées à des clés. Voici les
explications.
On ajoute les extras dans l’intent avant de démarrer l’activité. Il y a de nombreuses surcharges de
putExtra.
De l’autre côté, dans Activity2, il faut récupérer ces extras et les placer dans des variables. Voici
14
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
le principe :
val information = intent.getStringExtra("information")
val nombre = intent.getIntExtra("nombre", -1) // il faut une valeur par défaut
Attention au piège : le symbole intent est un raccourci Kotlin pour this.getIntent(). C’est
un getter pour récupérer l’intent qui a servi à lancer cette activité. C’est pour ça que les intents
qu’on crée nous-même sont nommés intentMachin, pour ne pas les confondre avec le intent de
l’activité.
Contrairement à putExtra, il y a autant de méthodes get***Extra que de classes sérialisables, et
beaucoup demandent des valeurs par défaut.
On applique cela aux deux activités, LoginActivity va envoyer un nom à MainActivity.
1. Dans la méthode onEditorAction de LoginActivity, ajoutez la chaîne username en tant
qu’extra pour lancer MainActivity.
2. Dans la méthode onCreate de MainActivity, ajoutez ce qu’il faut pour récupérer la chaîne
username présente dans l’intent. Puis faites afficher “Bonjour” + ce nom dans le TextView.
3. Lancez sur l’AVD. Saisissez votre nom. Pour valider, il faut cliquer sur le bouton représentant
un check dans le clavier virtuel.
companion object {
// nom de l'extra contenant le nom de connexion
val EXTRA_USERNAME = "username"
}
Cette syntaxe Kotlin indique de définir une variable de classe appelée EXTRA_USERNAME. En Java,
on écrirait simplement public static final String EXTRA_USERNAME = "username";
2. Dans la méthode onCreate de MainActivity,
remplacez val username = intent.getStringExtra("usrnam")
par val username = intent.getStringExtra(EXTRA_USERNAME)
3. Dans la méthode onEditorAction de LoginActivity, remplacez "username" par
MainActivity.EXTRA_USERNAME
4. Vérifiez le bon fonctionnement sur l’AVD.
15
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
import android.app.Application
3. Modifiez le fichier AndroidManifest.xml afin que votre application repose sur cette nouvelle
classe. Il suffit de rajouter l’attribut android:name à l’élément <application> ; ne vous
trompez pas entre activités et application :
Grâce à ça, cette classe et sa variable seront disponibles dans toutes les activités.
// récupérer le contexte d'application
val app = applicationContext as TP4Application
4. Dans MainActivity.kt
a. Définissez une variable membre lateinit var app: TP4Application,
b. Dans onCreate, initialisez-la comme ci-dessus mais sans le val,
c. Supprimez la variable membre compteur (ça crée des erreurs),
d. Remplacez partout compteur par app.compteur (ça enlève les erreurs).
5. Vérifiez que la variable compteur n’est plus remise à zéro à chaque changement d’orientation
de l’AVD. Elle n’est remise à zéro que quand on quitte complètement l’application et qu’on
la relance.
5. Travail à rendre
Important : votre projet doit se compiler et se lancer sur un AVD. La note du TP sera zéro
si ce n’est pas le cas. Mettez donc en commentaire ce qui ne compile pas. C’est impératif que
16
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24
l’application puisse être lancée même si elle ne fait pas tout ce qui est demandé.
Avec le navigateur de fichiers, descendez dans le dossier app/src du projet TP4. Cliquez droit
sur le dossier main, compressez-le au format zip. Déposez l’archive main.zip sur Moodle au bon
endroit, remise du TP4 sur la page Moodle R4.A11 Développement Mobile.
Rajoutez un fichier appelé exactement IMPORTANT.txt dans l’archive, si vous avez rencontré des
problèmes techniques durant le TP : plantages, erreurs inexplicables, perte du travail, etc. mais
pas les problèmes dus à un manque de travail ou de compréhension. Décrivez exactement ce qui
s’est passé. Le correcteur pourra lire ce fichier au moment de la notation et compenser votre note.
17