tp4

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 17

IUT de Lannion Programmation Android P.

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

2. Étude d’une activité


2.1. Création du projet
1. Créez un nouveau projet, TP4 de type « Empty Views Activity ».
2. Remplacez le contenu de activity_main.xml par ceci :

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:gravity="center"/>
<Button
android:id="@+id/bouton1"
android:layout_width="wrap_content"

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.

2.2. Classe MainActivity


1. Affichez la classe MainActivity
Une activité Android doit être une sous-classe de Activity. Ici, MainActivity est même une
sous-classe de AppCompatActivity qui dérive de Activity.
Une activité doit obligatoirement surcharger la méthode onCreate. Cette méthode est appelée
lorsque l’activité démarre et apparaît sur l’écran. Son rôle est de mettre en place l’interface.
2. Modifiez onCreate de cette manière (il y aura un import à ajouter pour Log) :

private val TAG = "TP4"

override fun onCreate(savedInstanceState: Bundle?) {


// initialisation interne de l'activité
super.onCreate(savedInstanceState)
// mise en place du layout activity_main
setContentView(R.layout.activity_main)
// titre de l'activité dans l'ActionBar
setTitle(localClassName)
// message d'information
Log.i(TAG, "dans ${localClassName}.onCreate")
}

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

Il y a une autre particularité de Kotlin. Le symbole localClassName est un raccourci pour


this.getLocalClassName(). Kotlin transforme les getters en pseudo variables membres. Ainsi
quand on écrit tv.text, en fait, c’est tv.getText(). On approfondira ça dans le TP5.
Il y a d’autres méthodes similaires à onCreate qu’on peut surcharger. Elles sont liées au cycle de
vie des activités, c’est à dire à ce qui se passe lors de la création et la destruction d’une activité.
3. Ajoutez ces méthodes :

override fun onDestroy() {


super.onDestroy()
Log.i(TAG, "dans ${localClassName}.onDestroy")
}

override fun onPause() {


super.onPause()
Log.i(TAG, "dans ${localClassName}.onPause")
}

override fun onResume() {


super.onResume()
Log.i(TAG, "dans ${localClassName}.onResume")
}

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

2.4. Liens entre activités et layouts


On revient dans MainActivity. On s’intéresse à ses liens avec le layout. Ce dernier est mis en
place par la méthode setContentView appelée dans onCreate. Le paramètre est l’identifiant du
layout, R.layout.son_nom.
Ensuite, on peut avoir accès aux vues du layout. Pour cela, on doit récupérer les objets corre-
spondants. Il faut savoir qu’un layout, un fichier XML, est expansé (inflated), c’est à dire traduit
sous forme d’objets par setContentView. Chaque balise du layout devient un objet généralement
visible à l’écran, configuré par les attributs présents dans le XML.
Pour récupérer ces objets dans des variables Kotlin, il y a deux techniques : la méthode
findViewById et les view bindings. Dans tous les cas, il faut que la vue recherchée ait un
identifiant android:id="@+id/identifiant".

2.4.1. Méthode findViewById

Il suffit de faire :
val variable: Type = findViewById(R.id.identifiant)

Par exemple, avec le layout qu’on a mis en place :


val bouton1: Button = findViewById(R.id.bouton1)

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

override fun onCreate(savedInstanceState: Bundle?) {


// initialisation interne de l'activité
super.onCreate(savedInstanceState)
// mise en place du layout activity_main
setContentView(R.layout.activity_main)
...
// récupération des vues
bouton2 = findViewById(R.id.bouton2)
}

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

val bouton1: Button? = findViewById(R.id.bouton1)

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.

2.4.2. Liaisons de vues (view bindings)

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

En clair, si vous définissez un layout appelé activity_search.xml contenant ceci :


<ConstraintLayout ...>
<TextView android:id="@+id/titre" .../>
<EditText android:id="@+id/texte" .../>
<Button android:id="@+id/search" .../>
</ConstraintLayout>

Alors Android Studio crée automatiquement une classe appelée ActivitySearchBinding.kt


contenant à peu près ceci :
class ActivitySearchBinding : ViewBinding() {
val titre: TextView
val texte: EditText
val search: Button

fun getRoot(): ConstraintLayout {


...
}
}

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

override fun onCreate(savedInstanceState: Bundle?) {


// initialisation interne de l'activité
super.onCreate(savedInstanceState)
// mise en place du layout par un view binding
ui = ActivityMainBinding.inflate(layoutInflater)
setContentView(ui.root)
// titre de l'activité dans l'ActionBar

6
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24

setTitle(localClassName)
...

4. Relancez l’application et vérifiez que ça n’a rien changé pour l’utilisateur.


Donc on remplace l’appel à setContentView par une affectation de ui suivi d’une autre manière
d’appeler setContentView.
Remarquez que ui.root est équivalent à ui.getRoot(), et aussi layoutInflater est un raccourci
pour this.getLayoutInflater(). Avec Kotlin, il devient difficile de savoir si un symbole est une
variable, ou un appel à un getter sur this.
Attention ne pas laisser l’ancien appel à setContentView.
Maintenant, la variable membre ui permet d’accéder aux vues. Par exemple :
ui.message.text = "Voici MainActivity"

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. Activité des vues


On voudrait maintenant qu’un clic sur le bouton change le message affiché.
Pour cela, il faut que le bouton déclenche l’appel d’une méthode dans l’activité : un écouteur. Cet
écouteur modifiera le message affiché.
Il y a plusieurs manières de définir un écouteur.

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

2. Définissez la méthode dans l’activité :

// variable membre
var compteur = 0

public fun onBouton1Click(view: View?) {


compteur += 1
ui.message.text = "compteur = ${compteur}"
}

3. Vérifiez que ça fonctionne : quand on clique sur le bouton, ça appelle la méthode.


Il faut savoir que cette façon de faire est dépréciée parce qu’elle oblige le système Android à
chercher la méthode dans la classe compilée, par introspection. D’autre part, la méthode doit être
publique, ce qui peut poser problème.

2.5.2. Définition d’un écouteur privé anonyme

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 :

override fun onCreate(savedInstanceState: Bundle?) {


...
// écouteur pour le bouton2, lambda
ui.bouton2.setOnClickListener {
compteur += 2
ui.message.text = "compteur = ${compteur}"
}
}

La syntaxe Kotlin objet1.setChose { ... } est un raccourci pour objet1.setChose(it -> {


... }). C’est à dire que ça construit une lambda à partir d’un bloc {...} et en lui passant un
paramètre appelé it (une variante de this). Ici, it n’est pas utilisé.
Cette technique consiste donc à définir des lambdas pour chaque composant actif de l’interface et
toutes les placer dans la méthode onCreate de l’activité. Cette façon de faire peut convenir à de
toutes petites actions lors des clics. Il y aura un problème de lisibilité et de modularité s’il y a de
nombreux composants à gérer ainsi, et/ou si les actions sont complexes.
En plus, ça sera impossible de faire des tests unitaires sur les actions des boutons.
D’autre part, une lambda ne peut être employée que lorsqu’il n’y a qu’une seule méthode à définir.
Or certains composants demandent un écouteur définissant deux méthodes, et on ne peut pas le
faire comme précédemment. En fait, la lambda précédente représente la syntaxe suivante, plus
générale, dite de la classe privée anonyme qu’on retrouve en Java aussi.

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 :

override fun onCreate(savedInstanceState: Bundle?) {


...
// écouteur pour le bouton3, classe privée anonyme
ui.bouton3.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
compteur += 3
//this.compteur += 3 // AIE : ici this n'est pas l'activité
//[email protected] += 3 // ça, c'est correct
ui.message.text = "compteur = ${compteur}"
}
})
}

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.

2.5.3. Définition d’une référence de méthode

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 :

fun onBouton4Click(view: View?) {


compteur *= 2
ui.message.text = "compteur = ${compteur}"
}

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 :

// mise en place du layout par un view binding


ui = ActivityInfosBinding.inflate(layoutInflater)
setContentView(ui.root)
// titre de l'activité
setTitle(localClassName)

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

3.1. Théorie : lancement et terminaison d’une activité


Voici des explications à lire. Il y a un exercice plus loin.
• Pour lancer une autre activité, par exemple ici Activity2 :

val intentA2 = Intent(this, Activity2::class.java)


startActivity(intentA2)

Voici deux particularités de la syntaxe Kotlin :


1. On ne met pas new pour créer une instance. On écrit seulement le nom de la classe et les
paramètres du constructeur, comme en Python.
2. Quand veut passer une classe en paramètre, on écrit Classe::class.java. Dans la plupart
des langages objets, les classes sont elles-mêmes des instances d’une classe spéciale, la classe
des classes. Alors l’écriture Activity2::class.java récupère l’objet qui représente la classe
de Activity2.
L’activité qui fait cela se retrouve en dessous de la nouvelle, Activity2, dans une sorte de pile
(activity stack). La nouvelle activité occupe tout l’écran avec son layout, et l’ancienne activité est
mise en pause.
• Quand une activité souhaite se terminer, elle doit juste faire ceci :

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

b. Appuyez sur le bouton 6 “infos+fin”, on doit aller sur InfosActivity


c. Appuyez sur le bouton back ◁. Ça doit ramener sur le bureau Android et non pas
dans MainActivity.
3. Maintenant, avec InfosActivity, faites en sorte que son bouton “Retour” ramène sur
MainActivity quand celle-ci est vivante en dessous de InfosActivity (lancement avec
“infos+retour”), et sur le bureau si ce n’est plus le cas (lancement avec “infos+fin”) ?
C’est une question piège. Ce bouton ne doit pas recréer d’intent pour démarrer MainActivity,
mais simplement faire finish(). Que se passerait-il si le bouton recréait un intent et qu’on
re-clique sur le bouton d’infos : la pile d’activité se remplirait d’une alternance de MainActivity
et InfosActivity.
4. Testez les séquences supplémentaires suivantes :
• Test 4
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 Retour. On doit revenir dans MainActivity.
d. Appuyez sur le bouton back ◁. Ça doit ramener sur le bureau Android et non pas
dans InfosActivity.
• Test 5
a. Lancez l’application sur un AVD, on doit voir MainActivity
b. Appuyez sur le bouton 6 “infos+fin”, on doit aller sur InfosActivity
c. Appuyez sur le bouton Retour. Ça doit ramener sur le bureau Android et non pas
dans MainActivity.

3.3. Autres types de lancements avec des Intent


Le concept d’Intent est extrêmement puissant. Cela permet de lancer autre chose que des activités
internes d’une application. Par exemple, voici comment lancer un navigateur sur un URL :
val url = "https://perso.univ-rennes1.fr/pierre.nerzic/Android"
val intentURL = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intentURL)

1. Ajoutez un nouveau bouton dans activity_infos.xml ayant pour titre “Cours”.


2. Définissez un écouteur dans InfosActivity qui lance ces instructions (l’intent qui ouvre
l’URL du cours).
Un intent demande une action au système Android. Il y a toutes sortes d’actions possibles, voir la
documentation. Dans l’exemple précédent, c’est l’affichage d’un URL. On a aussi des recherches,
des éditions, des envois de messages, etc.
Quand on crée un intent, on indique le type d’action et on fournit les paramètres nécessaires.
Ensuite, le système Android cherche une application capable de traiter cet intent. Par exemple,
pour une ACTION_VIEW, il va chercher un navigateur internet, par exemple Chrome.
Ce sont les applications comme Chrome ou autres qui déclarent dans leur manifeste ce qu’elles
sont capables de faire. Par exemple, Chrome s’est déclaré capable d’afficher des URL, un logiciel
de mail se déclarera capable d’envoyer des messages et d’en recevoir, un afficheur de documents
dira qu’il peut afficher des pdf et des epub, etc.

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

4. Transmission d’informations entre activités


Maintenant, on va étudier le transfert d’une information entre deux activités.
1. Création d’une nouvelle activité :
• Créez une nouvelle activité de type Empty Views Activity,
• Appelez-la LoginActivity. Elle aura un layout appelé activity_login.xml.
• Refaites l’attribution du layout par view bindings et l’affichage du titre comme dans le
début de MainActivity.onCreate.
2. Modification du manifeste :
• Cette nouvelle activité a été ajoutée dans AndroidManifest.xml.
• Maintenant elle qui doit être la principale. Donc :
– Déplacez la balise (et son contenu) <intent-filter> qui étaient pour
MainActivity vers LoginActivity,
– Modifiez les attributs exported="true" pour celle qui a l’intent, false pour les
autres.
3. Définition du layout :
• Mettez ce contenu dans activity_login.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".LoginActivity">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bonjour, qui êtes-vous ?"
android:layout_marginTop="120dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:gravity="center"/>
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:hint="Votre pseudo"
android:inputType="text"
android:imeOptions="actionDone"/>
</LinearLayout>

4. Définition de la méthode onCreate et d’un écouteur dans LoginActivity :

13
IUT de Lannion Programmation Android P. Nerzic
Dept Informatique TP4 - Activités 2023-24

// interface utilisateur
lateinit var ui: ActivityLoginBinding

override fun onCreate(savedInstanceState: Bundle?) {


// initialisation interne de l'activité
super.onCreate(savedInstanceState)
// mise en place du layout par un view binding
ui = ActivityLoginBinding.inflate(layoutInflater)
setContentView(ui.root)
// titre de l'activité
setTitle(localClassName)
// écouteur quand on valide la saisie du pseudo
ui.username.setOnEditorActionListener(this::onEditorAction)
}

private fun onEditorAction(textView: TextView?,


actionId: Int, keyEvent: KeyEvent?): Boolean
{
// récupérer le texte tapé dans l'EditText
val username = ui.username.getText().toString()
// TODO lancer MainActivity avec username en tant qu'extra
...
// TODO empêcher un retour dans LoginActivity par le bouton back
...
// pour empêcher un double appel de cet écouteur
ui.username.setOnEditorActionListener(null)
return true
}

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.

4.1. Transmission d’extra d’une activité à l’autre

Voici le principe général :


val information: String = ...
val nombre: Int = ...
val intentA2 = Intent(this, Activity2::class.java)
intentA2.putExtra("information", information)
intentA2.putExtra("nombre", nombre) // putExtra a plein de surcharges
startActivity(intentA2)

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.

4.2. Amélioration pour éviter des bugs


La solution précédente est assez fragile. Voici pourquoi :
1. Dans MainActivity.kt, changez l’instruction
val username = intent.getStringExtra("username") par
val username = intent.getStringExtra("usrnam").
C’est une fatue de frappe, et alors ?
2. Lancez l’application sur l’AVD, saisissez votre nom, validez. . .
Le problème est que l’extra qu’on a placé dans l’intent est identifié par une chaîne, mais on n’a
pas mis la même à l’envoi et à la réception.
Voici comment faire mieux :
1. Dans MainActivity.kt, ajoutez ceci au tout début :

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

4.3. Optionnel Contexte d’application


Le contexte d’application est une classe héritant de Application qui permet de stocker une
information globale de l’application. Android crée automatiquement un singleton de cette classe,
c’est à dire une instance unique pour toute la vie de l’application. Elle est disponible dans
n’importe quelle activité à l’aide de la méthode getApplicationContext(). C’est un peu comme
une session en PHP. Mais, par défaut le contexte créé par Android ne contient rien, donc nous
allons le surcharger.
1. Lancez l’application sur l’AVD, appuyez plusieurs fois sur les boutons 1 à 4 pour augmenter
la valeur affichée. Ensuite, faites pivoter l’AVD en mode paysage, puis revenez en portrait.
Constatez que le compteur est remis à zéro à chaque mouvement de l’AVD.
2. Définissez une nouvelle classe Kotlin appelée TP4Application contenant ceci :
package fr.iutlan.tp4

import android.app.Application

class TP4Application : Application() {


var compteur = 0
}

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 :

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>


...
<application
android:name=".TP4Application"
...

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

Vous aimerez peut-être aussi