Introduction à la programmation

de tablettes Android
par l'exemple

Serge Tahé, IstiA - université d'Angers
janvier 2014

http://tahe.developpez.com

1/157

Table des matières
1 APPRENTISSAGE DE LA PROGRAMMATION ANDROID.............................................................................................4
1.1 INTRODUCTION..........................................................................................................................................................................4
1.2 LA TABLETTE ANDROID.............................................................................................................................................................4
1.3 TESTS DES PROJETS ANDROID....................................................................................................................................................6
2 EXEMPLE-01 : VUE ET ÉVÈNEMENTS..............................................................................................................................8
2.1 CRÉATION DU PROJET................................................................................................................................................................8
2.2 L'ENCODAGE DES FICHIERS DU PROJET.....................................................................................................................................9
2.3 LE MANIFESTE DE L'APPLICATION.............................................................................................................................................9
2.4 L'ACTIVITÉ PRINCIPALE...........................................................................................................................................................11
2.5 EXÉCUTION DE L'APPLICATION................................................................................................................................................12
2.6 CONSTRUIRE UNE VUE.............................................................................................................................................................13
2.7 GESTION DES ÉVÉNEMENTS......................................................................................................................................................18
2.8 MODIFICATION DU MANIFESTE................................................................................................................................................20
3 EXEMPLE-02 : NAVIGATION ENTRE VUES...................................................................................................................22
3.1 CRÉATION DU PROJET..............................................................................................................................................................22
3.2 AJOUT D'UNE SECONDE ACTIVITÉ.............................................................................................................................................22
3.3 NAVIGATION DE LA VUE N° 1 À LA VUE N° 2............................................................................................................................25
3.4 CONFIGURATION DE L'ENVIRONNEMENT D'EXÉCUTION...........................................................................................................26
3.5 CONSTRUCTION DE LA VUE N° 2..............................................................................................................................................26
3.6 EXPLOITATION DES INFORMATIONS DE L'INTENT DE L'ACTIVITÉ.............................................................................................28
3.7 NAVIGATION DE LA VUE N° 2 VERS LA VUE N° 1.......................................................................................................................29
4 EXEMPLE-03 : CONSTRUIRE UN PROJET MAVEN / ANDROID................................................................................31
5 EXEMPLE-04 : MAVENISER UN PROJET ANDROID EXISTANT...............................................................................34
6 EXEMPLE-05 : NAVIGATION PAR ONGLETS................................................................................................................ 35
6.1 LE PROJET ANDROID...............................................................................................................................................................35
6.2 LES VUES.................................................................................................................................................................................35
6.3 L'ACTIVITÉ..............................................................................................................................................................................36
6.4 UN NOUVEAU FRAGMENT.........................................................................................................................................................40
6.5 DÉSACTIVER LE SWIPE OU BALAYAGE.....................................................................................................................................43
7 EXEMPLE-06 : LA NAVIGATION ENTRE VUES REVISITÉE...................................................................................... 47
7.1 LE PROJET...............................................................................................................................................................................47
7.2 MISE EN PLACE DE LA NAVIGATION.........................................................................................................................................50
7.3 CONCLUSION............................................................................................................................................................................51
8 EXEMPLE-07 : UNE ARCHITECTURE À DEUX COUCHES........................................................................................ 52
8.1.1 LE PROJET ANDROID...............................................................................................................................................................52
8.1.2 LA VUE [VUE_01]..................................................................................................................................................................52
8.1.3 LA VUE [ACTIVITY_MAIN].......................................................................................................................................................55
8.1.4 LA COUCHE [MÉTIER].............................................................................................................................................................56
8.1.5 L'ACTIVITÉ [MAINACTIVITY]..................................................................................................................................................57
8.1.6 LE FRAGMENT DE LA VUE [VUE_01].......................................................................................................................................59
8.1.7 EXÉCUTION............................................................................................................................................................................62
8.1.8 MAVENISATION DU PROJET......................................................................................................................................................62
9 EXEMPLE-08 : ARCHITECTURE CLIENT / SERVEUR.................................................................................................65
9.1 SPRING MVC..........................................................................................................................................................................65
9.1.1 LE PROJET ECLIPSE................................................................................................................................................................65
9.1.2 ANATOMIE D'UN PROJET SPRING MVC....................................................................................................................................67
9.2 LE SERVEUR REST.................................................................................................................................................................73
9.2.1 LA COUCHE [MÉTIER].............................................................................................................................................................74
9.2.2 LE SERVICE REST.................................................................................................................................................................76
9.2.3 EXÉCUTION DU SERVEUR REST.............................................................................................................................................80
9.3 LE CLIENT ANDROID DU SERVEUR REST................................................................................................................................81
9.3.1 LE PROJET ANDROID...............................................................................................................................................................81
9.3.2 LE MANIFESTE DE L'APPLICATION ANDROID.............................................................................................................................82
9.3.3 LA COUCHE [METIER].............................................................................................................................................................83
9.3.4 LA COUCHE [DAO]................................................................................................................................................................85
9.3.5 LA COUCHE [ANDROID]..........................................................................................................................................................88
9.3.6 EXÉCUTION DU CLIENT REST................................................................................................................................................90
10 EXEMPLE-09 : UN CLIENT REST ASYNCHRONE.......................................................................................................93
10.1 LA CLASSE [ASYNCTASK]......................................................................................................................................................93
10.2 LE PROJET ANDROID.............................................................................................................................................................94
10.3 LES ÉLÉMENTS DE L'INTERFACE ASYNCHRONE......................................................................................................................94
10.4 L'APPEL DE LA TÂCHE DANS LA VUE......................................................................................................................................96

http://tahe.developpez.com

2/157

10.5 MODIFICATION DE L'ACTIVITÉ..............................................................................................................................................99
10.6 EXÉCUTION DU CLIENT REST ASYNCHRONE.........................................................................................................................99
11 EXEMPLE-10 : ANNULATION D'UNE TÂCHE ASYNCHRONE...............................................................................100
11.1 L'ACTIVITÉ [MAINACTIVITY]..............................................................................................................................................100
11.2 LA VUE XML [VUE_01].......................................................................................................................................................101
11.3 LE FRAGMENT [VUE_01].....................................................................................................................................................102
12 EXEMPLE 11 : GESTION DE PLUSIEURS TÂCHES ASYNCHRONES...................................................................105
12.1 LE SERVEUR [REST]...........................................................................................................................................................105
12.1.1 LA COUCHE [MÉTIER].........................................................................................................................................................105
12.1.2 LE CONTRÔLEUR SPRING MVC..........................................................................................................................................107
12.2 LE CLIENT ANDROID DU SERVEUR REST............................................................................................................................108
12.2.1 LA COUCHE [DAO]............................................................................................................................................................109
12.2.2 LA COUCHE [MÉTIER].........................................................................................................................................................109
12.2.3 LA TÂCHE ASYNCHRONE.....................................................................................................................................................111
12.2.4 LE FRAGMENT [VUE_01]....................................................................................................................................................112
12.2.5 L'ACTIVITÉ [MAINACTIVITY]..............................................................................................................................................114
12.2.6 EXÉCUTION........................................................................................................................................................................114
13 EXEMPLE-12 : COMPOSANTS DE SAISIE DE DONNÉES........................................................................................115
13.1 LE PROJET ANDROID............................................................................................................................................................115
13.2 LA VUE XML DU FORMULAIRE............................................................................................................................................115
13.3 LES CHAÎNES DE CARACTÈRES DU FORMULAIRE..................................................................................................................121
13.4 LE FRAGMENT DU FORMULAIRE..........................................................................................................................................121
13.5 L'ACTIVITÉ [MAINACTIVITY].............................................................................................................................................125
14 EXEMPLE-13 : UTILISATION D'UN PATRON DE VUES...........................................................................................127
15 EXEMPLE-14 : LE COMPOSANT [LISTVIEW]...........................................................................................................131
15.1 LE PROJET ECLIPSE.............................................................................................................................................................131
15.2 LA VUE [VUE1] INITIALE.....................................................................................................................................................132
15.3 LA VUE RÉPÉTÉE PAR LE [LISTVIEW]..................................................................................................................................133
15.4 LE FRAGMENT [VUE1FRAGMENT].......................................................................................................................................134
15.5 L'ADAPTATEUR [LISTADAPTER] DU [LISTVIEW].................................................................................................................136
15.6 RETIRER UN ÉLÉMENT DE LA LISTE.....................................................................................................................................138
15.7 LA VUE XML [VUE2]..........................................................................................................................................................138
15.8 LE FRAGMENT [VUE2FRAGMENT].......................................................................................................................................139
15.9 EXÉCUTION..........................................................................................................................................................................141
15.10 AMÉLIORATION..................................................................................................................................................................141
16 EXEMPLE-16 : UTILISER UN MENU............................................................................................................................143
16.1 LA DÉFINITION XML DU MENU...........................................................................................................................................143
16.2 LA GESTION DU MENU DANS L'ACTIVITÉ [MAINACTIVITY].................................................................................................144
16.3 LA GESTION DU MENU DANS LE FRAGMENT [VUE1FRAGMENT]...........................................................................................144
16.4 LA GESTION DU MENU DANS LE FRAGMENT [VUE2FRAGMENT]...........................................................................................145
16.5 EXÉCUTION..........................................................................................................................................................................146
17 EXERCICE D'APPLICATION..........................................................................................................................................147
17.1 INTRODUCTION....................................................................................................................................................................147
17.2 INSTALLATION ET TEST DU SERVEUR REST.........................................................................................................................147
17.3 LES VUES DU CLIENT ANDROID............................................................................................................................................151
17.4 TRAVAIL À FAIRE..................................................................................................................................................................153
18 CONCLUSION.................................................................................................................................................................... 157

http://tahe.developpez.com

3/157

2 La tablette Android Vous aurez besoin par la suite de connecter votre tablette à un réseau wifi et de connaître son adresse IP sur ce réseau.developpez. Vous allez en avoir besoin .html]. une fois connecté au réseau. dans la section à gauche.zip]. sélectionnez un réseau wifi . pour avoir une vue d'ensemble de la programmation Android. Voici comment procéder : • • • • • allumez votre tablette . L'adresse IP de la tablette sera affichée.com/android/avat]. Les projets Eclipse des 16 exemples sont disponibles à l'URL [http://tahe. cherchez dans les applications disponibles sur la tablette (en haut à droite) celle qui s'appelle [paramètres] avec une icône de roue dentée . http://tahe.1 Apprentissage de la programmation Android 1. dans la section à droite. Les outils nécessaires pour développer une application Android sont décrits dans les annexes du document [http://tahe. Le site de référence pour la programmation Android est à l'URL [http://developer. paragraphe 11. Aussi n'est-il qu'un document de formation partielle à la programmation Android. Cela explique le ton parfois un peu particulier du texte.android. Il cible essentiellement les débutants.developpez. C'est là qu'il faut aller. Notez-la. L'outil de développement utilisé est STS (SpringToolSuite).developpez. un IDE basé sur Eclipse. faites une frappe courte sur le réseau sélectionné.com/guide/components/index.1 Introduction Ce document présente certains concepts d'Android au travers de 16 exemples : Exemple Nature 1 Vues et événements 2 Navigation entre vues 3 Construire un projet Maven / Android 4 Maveniser un projet Android existant 5 Navigation par onglets 6 Navigation entre vues revisitée 7 Architecture à deux couches 8 Architecture client / serveur 9 Un client REST asynchrone 10 Annulation d'une tâche asynchrone 11 Gestion de plusieurs tâches asynchrones 12 Composants de saisie de données 13 Utilisation d'un patron de vues 14 et 15 Le composant ListView 16 Utiliser un menu Exercice d'application Ce document est utilisé en dernière année de l'école d'ingénieurs IstiA de l'université d'Angers [ istia.com 4/157 . Ce document ne présente que les concepts nécessaires à ce TP.com/android/arduino].com/fichiers-archive/androidexemples. 1.ftp-developpez. activez le wifi .univ-angers.fr] comme document préparatoire à un TP présenté dans [http://tahe.

. sur votre PC.: fe80::698b:455a:925:6b13%4 Adresse IPv4.developpez. Maintenant.1.255.1. : 255. celle de la ligne 17 qui est celle du PC sur le réseau wifi .25 Masque de sous-réseau. . 6.Toujours dans l'application [Paramètres]. 15.1 Votre PC a deux cartes réseau et donc deux adresses IP : celle de la ligne 9 qui est celle du PC sur le réseau filaire . . . . . . . . . .168. . . dans une fenêtre DOS.168. 17.255.1. . : Carte réseau sans fil Wi-Fi : Suffixe DNS propre à la connexion. . vérifiez la connexion réseau avec la tablette avec une commande [ping 192. 18. . l'icône la plus à droite est celle de la gestion des tâches.0 Passerelle par défaut. . tapez dans la barre d'état en bas. tapez la commande [ipconfig] : 1.y La tablette aura à se connecter à votre PC. . . 13. . l'icône du milieu. . Vous pouvez voir et gérer toutes les tâches exécutées à un moment donné par votre tablette .1.com 5/157 . . . . 3. .168. .1 Masque de sous-réseau. .1.y] est l'adresse IP de la tablette.26 avec 32 octets de données : Réponse de 192. . . .1. . 12. • • Notez ces deux informations. 2. Pour revenir au menu. . .: 192. 7.1.26 Envoi d'une requête 'Ping' 192. • sélectionnez à gauche l'option [Options de développement] (tout en bas des options) . 3. . .0 Passerelle par défaut. Vous en aurez besoin. . Faites-le avec l'option [Panneau de configuration\Système et sécurité\Pare-feu Windows].168. . . .255. Parfois il faut de plus inhiber le pare-feu mis en place par l'antivirus. 9.168.1. . . 8. Toujours dans la barre d'état en bas. Il vous faut donc inhiber le pare-feu. 10. . Vous devez obtenir quelque chose qui ressemble à ceci : 1. 14. . . 16. .1. . .26 : octets=32 temps=244 ms TTL=64 http://tahe. • vérifiez qu'à droite l'option [Débogage USB] est cochée. . : 255. 19. . • • Reliez votre tablette à votre PC avec le câble USB qui l'accompagne.168.168. .168. . . : 192. 5. Ceci fait. . .y] où [192. . Cela dépend de votre antivirus. . dos>ipconfig Configuration IP de Windows Carte Ethernet Connexion au réseau local : Suffixe DNS propre à la connexion.: fe80::39aa:47f6:7537:f8e1%2 Adresse IPv4.255. . . 4. .x 192. 4. : Adresse IPv6 de liaison locale.: 192. . Installez la clé wifi sur l'un des ports USB du PC puis connectez-vous sur le même réseau wifi que la tablette. . . . dos>ping 192. . .2. 11. .168. .168. Vous êtes désormais dans la configuration suivante : PC Tablette Pare-feu 192. . celle d'une maison. . l'icône la plus à gauche est celle du retour en arrière : vous revenez à la vue précédente . . Celui est normalement protégé par un pare-feu qui empêche tout élément extérieur d'ouvrir une connexion avec le PC. 2. : Adresse IPv6 de liaison locale.

developpez. vous pouvez faire des impressions sans votre code [Android] : System. pour exécuter la ligne en entrant dans les méthodes si la ligne contient des appels de méthodes.1. 9.168.println(. Si le projet plante.26 : octets=32 temps=199 ms TTL=64 Réponse de 192. Moyenne = 139ms Les lignes 4-7 indiquent que la tablette d'adresse IP [192.26 : octets=32 temps=88 ms TTL=64 Statistiques Ping pour 192. Dans ce cas. reçus = 4. en [3]. Durée approximative des boucles en millisecondes : Minimum = 28ms.168. Ces logs sont très nombreux.) . Lorsque votre projet Android s'exécute. Ces affichages se retrouveront en vert dans la fenêtre [LogCat].. • [F5]. perdus = 0 (perte 0%). Vous pouvez également exécuter votre projet en mode débogage : 1 • • • 2 3 en [1].. quittez la perspective [Debug] pour revenir à une perspective [Java] [1] : http://tahe.1. Maximum = 244ms.1.26: Paquets : envoyés = 4. on met un point d'arrêt sur une ligne de code en double-cliquant à gauche de son n° .1. Positionnez le filtre des logs à [Error] pour diminuer leur nombre. pour continuer jusqu'au prochain point d'arrêt .26 : octets=32 temps=28 ms TTL=64 Réponse de 192.168.com 6/157 . Elle est nettement plus rapide que l'émulateur. 8.1.168. l'exception qui s'est produite sera affichée dans cette fenêtre. Réponse de 192.y] a répondu à la commande [ping]. au point d'arrêt faire : • [F6]. Celui-ci peut être utile lorsque le projet nécessite une connexion réseau entre la tablette et le PC et que cette connexion n'existe pas (absence de réseau wifi). vous êtes obligés d'utiliser l'émulateur. un certain nombre de logs sont émis sur la fenêtre [LogCat] [Window / ShowView / Other / Android / Logcat]. 6.168. 7.3 Tests des projets Android Pour tester vos projets Android utilisez prioritairement la tablette. • [F8]. 1. Une fois le débogage terminé. 12. pour exécuter la ligne sans entrer dans les méthodes si la ligne contient des appels de méthodes. 11. en [2].out. 10. exécutez votre projet en mode débogage . Dans la recherche d'une erreur.5.

consultez la fenêtre [Problems] [Window / Show View / Other / General / Problems] pour connaître la nature exacte du ou des problèmes rencontrés [2].com 7/157 .developpez. votre projet sera erroné à cause de problèmes de configuration notamment Maven. Dans ce cas.2 1 Parfois. http://tahe. Parfois des solutions sont proposées pour résoudre le problème.

Ce n'est qu'à partir de cette version que certaines des classes que nous allons utiliser ont été disponibles . en [4]. on remplit les champs d'identité de l'application . • 3 5 4 • • • en [3].1 Création du projet 2 1 en [1-2]. on garde les valeurs proposées par défaut sauf pour le champ [Minimum Required SDK] qui fixe la version la plus ancienne d'Android sur laquelle l'application peut être exécutée. Le projet créé est le suivant : http://tahe. On utilisera dans ce document la version minimale 11.com 8/157 . on crée un projet de type [Android Application Project] .developpez.2 Exemple-01 : vue et évènements Créons avec STS un premier projet Android : 2. on valide les valeurs par défaut de l'assistant jusqu'à la dernière page [5] .

3 en [1]. Pour cela. choisir [UTF-8] . le serveur enverra des chaînes de caractères pouvant contenir des caractères accentués. Nous utiliserons l'encodage UTF-8.2. Le manifeste de l'application http://tahe.developpez. Il faut que le client et le serveur soient d'accord sur le type d'encodage utilisé pour les caractères dans les chaînes de caractères échangées.2 L'encodage des fichiers du projet Dans les applications client / serveur que nous allons créer. Pour vous en assurer. vos projets doivent être encodés en UTF-8.com 9/157 . procédez de la façon suivante : [clic droit sur le projet / Properties / Resource] : 1 • 2.

android:allowBackup="true" 13. <activity 17. ligne 14 : le libellé de l'aplication. android:name="istia. Son contenu est ici le suivant : 1. package="istia.android" 4. <string name="app_name">exemple-01</string> 4. des fragments [Fragment] et d'une activité de type [FragmentActivity] . 7. <manifest xmlns:android="http://schemas. </intent-filter> 24. Mettre la dernière version de cet OS .android. <string name="hello_world">Hello world!</string> 6.android. <uses-sdk 8. Elle peut être changée . ligne 8 : la version minimale d'Android pouvant exécuter l'application. </resources> Le fichier [strings. 27. http://tahe. 22. <string name="action_settings">Settings</string> 5. une version récente est nécessaire pour disposer des onglets.xml] [4] : 1. </application> 26.xml] contient les chaînes de caractères utilisées par l'application. android:icon="@drawable/ic_launcher" 14. android:versionCode="1" 5. Il se trouve dans le fichier [strings. <application 12.MainActivity" 18. <intent-filter> 20. Ici la version 11.MAIN" /> 21.developpez.st.intent. android:theme="@style/AppTheme" > 16. android:label="@string/app_name" 15.st.com/apk/res/android" 3. <action android:name="android. <?xml version="1. ligne 13 : l'icône [3] de l'application.category.xml] [1] fixe les caractéristiques de l'application Android. <?xml version="1.action.0" encoding="utf-8"?> 2. Un certain nombre de classes seront automatiquement générées dans ce paquetage [2] . </manifest> • • • • • ligne 3 : le paquetage du projet Android. </activity> 25. <resources> 3. android:label="@string/app_name" > 19. 11. android:versionName="1.3 2 4 1 Le fichier [AndroidManifest.com 10/157 . android:minSdkVersion="11" 9.LAUNCHER" /> 23. android:targetSdkVersion="16" /> 10.0" encoding="utf-8"?> 2. <category android:name="android.0" > 6.intent. ligne 9 : la version maximale d'Android.

@Override 16. setContentView(R.com 11/157 .view. menu). ligne 18 : son libellé .layout. --> 6. 9. } • • • ligne 7 : la classe [MainActivity] étend la classe [Activity]. @Override 10. 5. C'et toujours le cas.xml] [4] : 1. C'est avant l'affichage de la vue associée à l'activité .menu. Une activité peut afficher une ou plusieurs vues selon son type exact. 18. package istia. 2. public class MainActivity extends Activity { 8.Light"> 3.Activity. 19. import android. ligne 17 : le nom complet de la classe de l'activité . } 21. Une application Android peut avoir plusieurs activités .• ligne 15 : le style de l'interface visuelle.inflate(R. ligne 10 : la méthode [onCreate] est exécutée lorsque l'activité est créée. 15. Elle est définie dans le fichier [styles. } 14. <style name="AppTheme" parent="AppBaseTheme"> 7. </style> 4. 6. import android.4 ligne 16 : une balise d'activité. 22. ligne 20 : l'activité est désignée comme étant l'activité principale .Menu. return true. 5. super. <style name="AppBaseTheme" parent="android:Theme.main. import android. 20. // Inflate the menu. http://tahe. il faut toujours le faire . this adds items to the action bar if it is present. La classe [MainActivity] générée est la suivante : 1. </resources> • • • • • 2. 12. 4.android.onCreate(savedInstanceState).Application theme.Bundle. getMenuInflater().os. <resources> 2. 7.app. public boolean onCreateOptionsMenu(Menu menu) { 17. <!-.st.activity_main).developpez. </style> 8. Ici une activité [1] a été générée : [MainActivity]. ligne 11 : la méthode [onCreate] de la classe parente est appelée. 9. protected void onCreate(Bundle savedInstanceState) { 11. 3. L'activité principale 3 1 2 Une application Android repose sur une ou plusieurs activités. 13. ligne 22 : et elle doit apparaître dans la liste des applications qu'il est possible de lancer sur l'appareil Android.

2. il nous faut créer une configuration d'exécution : 5 4 1 • • • • • 2 3 en [1].com/tools" c) android:layout_width="match_parent" d) android:layout_height="match_parent" e) android:paddingBottom="@dimen/activity_vertical_margin" f) android:paddingLeft="@dimen/activity_horizontal_margin" g) android:paddingRight="@dimen/activity_horizontal_margin" h) android:paddingTop="@dimen/activity_vertical_margin" i) tools:context=".. il va être affiché en haut et à gauche du conteneur. en [3].5 Exécution de l'application Pour exécuter une application Android. http://tahe.xml] [3] : a) <?xml version="1. lignes k-n : un composant de type [TextView] qui sert à afficher du texte .com 12/157 .] .] . Il est tiré du fichier [strings. en [5]..MainActivity" > j) k) <TextView l) android:layout_width="wrap_content" m) android:layout_height="wrap_content" n) android:text="@string/hello_world" /> o) p) </RelativeLayout> • • • lignes a-p : le gestionnaire de mise en forme.0" encoding="utf-8"?> b) <resources> c) d) <string name="app_name">exemple-01</string> e) <string name="action_settings">Settings</string> f) <string name="hello_world">Hello world!</string> g) h) </resources> Le texte affiché sera donc [Hello world!].android..android. C'est le conteneur conseillé . sélectionnez le type [Android Application] puis l'icône [New launch configuration] .developpez. Où sera-t-il affiché ? Comme rien n'est indiqué. sélectionnez l'icône [Run as. Peut être quelconque . en [4]. ligne n : le texte affiché. donnez un nom à cette configuration. les composants sont placés les uns par rapport aux autres. en [2].xml] [2] est la vue associée à l'activité.com/apk/res/android" b) xmlns:tools="http://schemas. La définition XML de cette vue est la suivante : a) <RelativeLayout xmlns:android="http://schemas.• ligne 12 : le fichier [activity_main.. indiquez le projet qui sera exécuté par cette configuration . Dans ce type de conteneur. Celui qui a été choisi par défaut est le type [RelativeLayout]. sélectionnez l'option [Run Configurations.

sélectionnez la tablette Android et testez l'application. 8 9 • • • en [8]. sélectionnez l'émulateur de tablette et exécutez l'application . http://tahe. exécutez-la . Construire une vue Nous allons maintenant modifier la vue affichée avec l'éditeur graphique d'Eclipse ADT (Andoid Developer Tools). en [8]. Si aucun émulateur n'est lancé.com 13/157 . un émulateur de tablette. en [9]. validez cette configuration . sélectionnez l'option [7]. lancez l'émulateur appelé [Tablet] .6 8 9 7 • • • dans l'onglet [Target] [6]. L'émulateur logiciel affiche au bout d'un moment la vue suivante : Branchez maintenant une tablette Android sur un port USB du PC et exécutez l'application sur celle-ci : 1 • 2. en [9].developpez. Elle permet de choisir le mode d'exécution : en mode émulation avec une tablette logicielle ou en mode réel avec une tablette Android . une tablette Android .6 en [1].

1 • • 2 3 en [1]. android:layout_height="match_parent" > 5. " à gauche de ". <?xml version="1. </RelativeLayout> ligne 2 : un conteneur [RelativeLayout] vide qui occupera toute la largeur de la tablette (ligne 3) et toute sa hauteur (ligne 4) . mettez-vous à l'échelle 1 de la tablette . Ici. nous choisissons un conteneur [RelativeLayout]. nommez la vue . en [2]. android:layout_width="match_parent" 4. " au-dessous de ". en [3]. en [3]. en [2]. mettez-vous en mode tablette . • • • Le fichier [vue1.com/apk/res/android" 3. indiquez la balise racine de la vue.4 2 3 1 en [1].developpez. " audessus de " . sélectionnez l'onglet [Graphical Layout] . en [3].com 14/157 . prendre un [TextView] et le tirer sur la vue [2] .xml] généré [4] est le suivant : 1. fixer le texte du composant .0" encoding="utf-8"?> 2. • 3 2 1 • • • en [1]. Dans ce conteneur de composants. <RelativeLayout xmlns:android="http://schemas. ceux-ci sont placés les uns par rapport aux autres : " à droite de ".android. créez une nouvelle vue XML [clic droit sur layout / New / Other / Android/ Android Layout XML File] . http://tahe.

modifier l'identifiant du composant .xml] . la nouvelle vue . 9 10 8 • • • en [8]. en [9]. on veut créer une nouvelle chaîne de caractères dans le fichier [strings. en [10]. <RelativeLayout xmlns:android="http://schemas. ici 50 pixels . android:layout_width="match_parent" 4. android:id="@+id/textView_titre" 8.com/apk/res/android" 3. android:layout_width="wrap_content" 9. le texte de la chaîne de caractères créée . android:layout_height="match_parent" > 5. <?xml version="1.com 15/157 .android. mettre une taille. en [6]. 6.0" encoding="utf-8"?> 2. 12 11 • en [11] et [12]. modifier la taille du texte . Le fichier [vue1. en [5]. en [7]. l'identifiant de la chaîne de caractères créée .xml] a évolué comme suit : 1.developpez. android:layout_height="wrap_content" http://tahe. l'interface visuelle se met à jour .5 6 7 4 • • • • en [4]. <TextView 7.

android:layout_alignParentLeft="true" 11. Les autres attributs du [TextView] sont des valeurs par défaut ou bien découlent du positionnement du composant dans la vue . 13 : le haut du composant est aligné avec le haut de la vue (ligne 11). 6. 278 pixels à droite (ligne 12) . android:layout_alignParentLeft="true" 11. android:text="@string/vue1_titre" 15. android:textSize="50sp" /> 16.0" encoding="utf-8"?> 2. android:layout_marginLeft="88dp" 13.com 16/157 . <TextView 7. lignes 8-9 : la taille du composant est celle du texte qu'elle contient (wrap_content) en hauteur et largeur . droite. les tailles exactes des marges gauche. <RelativeLayout xmlns:android="http://schemas. android:layout_marginTop="26dp" 14. 17. 17. haute et basse seront fixées directement dans le XML. android:layout_height="wrap_content" 10. android:layout_height="match_parent" > 5. <?xml version="1. android:layout_marginLeft="278dp" 13.10. <TextView http://tahe. 12 : le côté gauche du composant est aligné avec la gauche de la vue (ligne 10). android:textSize="50sp" /> 16. </RelativeLayout> • • • • les modifications faites dans l'interface graphique sont aux lignes 7. créez la vue suivante [1] : 6 1 4 2 3 5 Les composants sont les suivants : N° Id 1 textView_titre 2 textView_nom 3 editText_Nom 4 button_valider 5 button_vue2 Type TextView TextView EditText Button Button Rôle Titre de la vue un texte saisie d'un nom pour valider la saisie pour passer à la vue n° 2 Le fichier XML est le suivant : 1. android:id="@+id/textView_titre" 8. android:layout_alignParentTop="true" 12. 14 et 15. lignes 10.com/apk/res/android" 3.android. android:layout_alignParentTop="true" 12. lignes 11. android:layout_width="wrap_content" 9. android:text="@string/vue1_titre" 15. En procédant de la même façon. En général. android:layout_marginTop="77dp" 14. android:layout_width="match_parent" 4.developpez. 77 pixels dessous (ligne 13) .

android:layout_marginLeft="50dp" 47. android:layout_toRightOf="@+id/editText_nom" 48. android:id="@+id/textView_nom" 19. android:layout_marginTop="35dp" 25. android:layout_marginTop="25dp" 57. android:layout_alignBaseline="@+id/editText_nom" 45. </RelativeLayout> • • • • lignes 17-25 : le composant [textView_nom] est positionné sous le composant [textView_titre] (ligne 22) à une distance de 35 pixels (ligne 24) . android:layout_marginLeft="49dp" 33. android:layout_height="wrap_content" 21. http://tahe. ligne 54 : le côté gauche du composant [button_vue2] est aligné avec le côté gauche du composant [textView_nom] . 40. android:layout_toRightOf="@+id/textView_nom" 34. android:text="@string/textView_nom" /> 26. android:layout_width="wrap_content" 43. </EditText> 39.developpez. 50. android:layout_width="wrap_content" 30. android:layout_below="@+id/textView_nom" 56. 27. Cet attribut est obtenu de la façon suivante (clic droit sur le composant / InputType) : lignes 40-48 : le composant [button_valider] est positionné à droite du composant [editText_Nom] (ligne 47) à une distance de 50 pixels (ligne 46) . android:id="@+id/editText_nom" 29. android:layout_alignLeft="@+id/textView_nom" 55. android:layout_below="@+id/textView_titre" 23. android:layout_alignBottom="@+id/editText_nom" 46.18. android:layout_marginLeft="45dp" 24. <Button 41. <EditText 28. android:inputType="text" > 36. android:layout_height="wrap_content" 44. 59. android:layout_alignBottom="@+id/textView_nom" 32. android:layout_width="wrap_content" 20. android:text="@string/btn_Valider" /> 49. android:layout_height="wrap_content" 54. android:layout_alignParentLeft="true" 22. android:text="@string/btn_vue2" /> 58.com 17/157 . android:id="@+id/button_valider" 42. android:id="@+id/button_vue2" 52. <Button 51. 37. android:layout_width="wrap_content" 53. android:ems="10" 35. android:layout_height="wrap_content" 31. lignes 27-38 : le composant [editText_Nom] est positionné à droite du composant [textView_nom] (ligne 33) à une distance de 49 pixels (ligne 32). Il a un attribut inputType ligne 35. <requestFocus /> 38.

vue1).xml] qui est désormais affichée . <string name="textView_nom">Quel est votre nom :</string> 9. } • ligne 4 : c'est la vue [vue1. <?xml version="1.0" encoding="utf-8"?> 2. Exécutez l'application et vérifiez que c'est bien la vue [vue1. 4.xml] [6] suivant : 1. • Tous les textes proviennent du fichier [strings. modifions l'activité [MainActivity] pour que cette vue soit affichée au démarrage de l'application : 1. <string name="app_name">exemple-01</string> 5.lignes 55-56 : le composant [button_vue2] est positionné à 25 pixels au-dessous du composant [textView_Nom] . <string name="vue1_titre">Vue n° 1</string> 8.com 18/157 . super.xml] qui est affichée : 2. protected void onCreate(Bundle savedInstanceState) { 3.onCreate(savedInstanceState). 12. setContentView(R.developpez. 4. <string name="btn_Valider">Validez</string> 10. </resources> Maintenant. @Override 2. 5. <resources> 3. <string name="hello_world">Hello world!</string> 7.7 Gestion des événements Gérons maintenant le clic sur le bouton [Validez] de la vue [Vue1] : http://tahe.layout. <string name="btn_vue2">Vue n° 2</string> 11. <string name="action_settings">Settings</string> 6.

public void onClick(View arg0) { 32.setOnClickListener(new OnClickListener() { 30. setContentView(R. 20.id. 4. 36.developpez. 10. protected void onCreate(Bundle savedInstanceState) { 14. public class MainActivity extends Activity { 6. 9. 15. 3.button_vue2). navigateToView2(). // bouton [Valider] 22..button_valider). 5. public void onClick(View arg0) { 25. private Button btnValider. doValider(). // TODO Auto-generated method stub http://tahe. 11. package istia.com 19/157 . btnVue2 = (Button) findViewById(R. @Override 13. btnValider.Le code de [MainActivity] évolue comme suit : 1. // on récupère les composants de la vue 17. btnValider = (Button) findViewById(R. }). } 35.android. 16. // gestionnaire d'évts 21.layout. btnVue2. } 27. private Button btnVue2. 37. .id. 12. // bouton [Vue2] 29. super. 39.editText_nom)..setOnClickListener(new OnClickListener() { 23. @Override 31. edtNom = (EditText) findViewById(R. 28.vue1). private EditText edtNom.id.st. 34. 2. }).onCreate(savedInstanceState). protected void navigateToView2() { 40. } 38. 18. @Override 24. 26. // on passe à la vue n° 2 33. 19. // les champs de la vue 8. 7.

return true. Exécutez le projet et vérifiez qu'il se passe quelque chose lorsque vous cliquez sur le bouton [Validez]. 46. edtNom. 48. ligne 50 : la méthode qui gère ce clic . String. 43. ligne 14 : la méthode du parent est appelée. } • • • • • • • • ligne 13 : la méthode exécutée lorsque l'activité est créée. • le troisième paramètre est la durée de vie de la boîte affichée : Toast. lignes 22-27 : on définit un gestionnaire pour l'événement clic sur le bouton [Valider] . 52. 47. On implémente une interface I avec une classe anonyme avec le code suivant : new I(){ // implémentation des méthodes de l'interface I . Les lignes 22-27 et 29-35 implémentent une interface avec une classe anonyme.. } 54. 50.LENGTH_SHORT .makeText(.getText(). 42.developpez. 44. this adds items to the action bar if it is present.format("Bonjour %s".41. On peut éviter cet affichage en modifiant le fichier [AndroidManifest.inflate(R. ligne 19 : on récupère la référence du composant d'id [button_vue2] . getMenuInflater(). ligne 18 : on récupère la référence du composant d'id [button_valider] . C'est obligatoire .com 20/157 . Toast. 53. 45. • le 1er paramètre de makeText est l'activité. } Lignes 22-27 : • la méthode [setOnClickListener] admet comme paramètre un type implémentant l'interface [OnClickListener] qui a une unique méthode [onClick]. } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu.LENGTH_LONG ou Toast. ligne 17 : on récupère la référence du composant d'id [editText_nom] .menu. Le paramètre de cette méthode est une référence sur le composant qui a été cliqué . 49. On peut arriver alors à la création d'un grand nombre de classes pour gérer les divers événements de l'interface . 2.).main. la vue s'affiche dans la tablette avec le focus sur la zone de saisie du nom.toString()). } protected void doValider() { // on affiche le nom saisi Toast. on a d'autres solutions : • définir une classe implémentant l'interface [OnClickListener]. • faire que l'activité [MainActivity] implémente toutes les interfaces dont on peut avoir besoin pour la gestion de ses événements. menu). • le second paramètre est le texte à afficher dans la boîte qui va être affichée par makeText.. Le clavier logiciel est alors automatiquement affiché.show().xml] : http://tahe.. Ce n'est pas esthétique car cela cache une partie de la vue. Cela limite alors la réutilisation de la classe . 51.. On l'utilise souvent pour récupérer des références sur les composants de la vue qui va être affichée .8 Modification du manifeste Lorsqu'on lance l'exécution du projet [exemple-01].LENGTH_LONG).makeText(this.show() : affiche un texte à l'écran. • sans la méthode de la classe anonyme. ligne 51 : affiche le nom saisi : • Toast.

<action android:name="android. http://tahe. android:label="@string/app_name" 15.intent.MainActivity" 18. <manifest xmlns:android="http://schemas.com 21/157 . </activity> 26.android" 4. android:allowBackup="true" 13. <application 12.android.st. android:label="@string/app_name" 19. android:icon="@drawable/ic_launcher" 14. <activity 17. </intent-filter> 25. <intent-filter> 21. Vérifiez-le.category. android:targetSdkVersion="16" /> 10.0" > 6.st. </application> 27.MAIN" /> 22.0" encoding="utf-8"?> 2. android:minSdkVersion="11" 9.com/apk/res/android" 3. android:name="istia. <category android:name="android. android:versionCode="1" 5. 11.developpez. package="istia. android:theme="@style/AppTheme" > 16. <uses-sdk 8. 28.android.LAUNCHER" /> 24. 7. 23. android:versionName="1. android:windowSoftInputMode="stateHidden" > 20.intent. <?xml version="1. </manifest> C'est la ligne 19 qui empêche l'affichage du clavier logiciel au démarrage de l'application.action.1.

3 4 1 2 • • • • en [1]. et un dossier existant ou non. il sera créé . 5 • 3.3 Exemple-02 : navigation entre vues Dans le projet précédent. le bouton [Vue n° 2] n'a pas été exploité. On se propose de l'exploiter en créant une seconde vue et en montrant comment naviguer d'une vue à l'autre. on lui donne un nom .1 Création du projet Comme le nouveau projet est une extension du précédent. en [3]. Il y a plusieurs façons de résoudre ce problème. on copie le projet [exemple-01] . Une autre méthode est d'avoir une unique activité de type [FragmentActivity] qui affiche des vues de type [Fragment]. C'est elle qui gèrera la vue n° 2. On est là dans un modèle une vue = une activité. S'il existe. Ce sera la méthode utilisée dans des applications à venir. il doit être vide. S'il n'existe pas. http://tahe. on le colle dans l'explorateur de projets . en [2]. 3.developpez. nous allons créer une seconde activité.com 22/157 . le nouveau projet.2 en [5]. nous allons dupliquer le projet [exemple-01] dans le projet [exemple-02]. en [4]. Celle qui est proposée ici est d'associer chaque vue à une activité. Il y a d'autres modèles. Ajout d'une seconde activité Pour gérer une seconde vue.

en [8]. en [5]. en [9]. 5 6 4 • • • • en [4]. Ce sera le nom de sa classe . donner un nom à la vue associée.developpez. validez par [Finish]. prendre une activité vide . Le manifeste a enregistré la nouvelle activité : http://tahe. la vue qui lui a été associée . la nouvelle activité . donner un nom à l'activité. le manifeste a été modifié.1 3 2 • créez une nouvelle activité Android [1-3] . 7 8 9 • • • en [7]. en [6]. Ici la vue sera [vue2.xml] .com 23/157 .

} 14.0" encoding="utf-8"?> 2. import android. return true. android:versionCode="1" 5.MainActivity" 18. package istia.android" 4.0" > 6.st.onCreate(savedInstanceState). <uses-sdk 8. 18. 32. 19. </intent-filter> 25.LAUNCHER" /> 24.inflate(R. // Inflate the menu. 13.com 24/157 . } 21.MAIN" /> 22. </activity> 26. android:versionName="1. @Override 16. } • • lignes 10-12 : lorsque l'activité est créée. <manifest xmlns:android="http://schemas.Activity. setContentView(R. 7.st.Menu. 12. 15. Ces lignes peuvent être suprimées.developpez. <activity 17. <activity 27.android.Bundle. <category android:name="android. menu). La classe [SecondActivity] générée est la suivante : 1.intent. 23. </manifest> • lignes 26-29 : la nouvelle activité.st. android:minSdkVersion="11" 9. @Override 10.second. android:theme="@style/AppTheme" > 16.layout.android. elle affiche la vue [vue2.android. <action android:name="android. 11. 4. 6. http://tahe.xml] (ligne 12) . import android. <application 12. android:label="@string/app_name" 19. android:name="istia. android:allowBackup="true" 13. 20. getMenuInflater().category.vue2). android:label="@string/app_name" 15.android. android:targetSdkVersion="16" /> 10. android:icon="@drawable/ic_launcher" 14. <intent-filter> 21. <?xml version="1.st. Nous n'en avons pas.os.app. lignes 15-20 : gèrent un éventuel menu.action. protected void onCreate(Bundle savedInstanceState) { 11. </activity> 30.1.intent. 7. package="istia. android:name="istia.com/apk/res/android" 3.SecondActivity" 28. 2. 22. 3.view. </application> 31. android:label="@string/title_activity_second" > 29. super. 5. public class SecondActivity extends Activity { 8. import android.menu. 9. this adds items to the action bar if it is present. public boolean onCreateOptionsMenu(Menu menu) { 17. android:windowSoftInputMode="stateHidden" > 20.

21. btnVue2 = (Button) findViewById(R. 18. } }). btnValider = (Button) findViewById(R. 10. } • @Override protected void onCreate(Bundle savedInstanceState) { super.La vue [vue2.vue1). } protected void navigateToView2() { // TODO Auto-generated method stub lignes 18-24 : le clic sur le bouton [Vue n° 2] est géré par la méthode [navigateToView2] de la ligne 29. 20. 9.layout.button_vue2). 4. 8. 16. 22. C'est là que nous allons installer le code de navigation. Le passage à la vue n° 2 est pour l'instant géré de la façon suivante : 1. 30. 5. // gestionnaire d'évts // bouton [Valider] btnValider.id.button_valider). // on récupère les composants de la vue edtNom = (EditText) findViewById(R. } }). 29.developpez.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { doValider().id.3 Navigation de la vue n° 1 à la vue n° 2 Revenons au code de la classe [MainActivity] qui affiche la vue ° 1. 6. 23. 2. 15. 24.xml] est la suivante : 3.onCreate(savedInstanceState). setContentView(R. 13.com 25/157 . 14. 26.editText_nom).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // on passe à la vue n° 2 navigateToView2(). 17. 25. 11. 12. 28. 19. 3. 27. 7.id. Le code de navigation vers la vue n° 2 sera le suivant : http://tahe. // bouton [Vue2] btnVue2.

developpez. 3. mettre des informations dans l'objet [Intent]. // on navigue vers la vue n° 2 en lui passant le nom saisi dans la vue n° 1 4. Nous modifions la vue [vue2. Celles-ci sont destinées à l'activité [SecondActivity] qui va être lancée. Nommez-la [exemple-02]. 6. 4.putExtra("NOM".4 Configuration de l'environnement d'exécution Nous avons suffisamment de code pour un test. ligne 7 : associer l'Intent à une activité. 3.xml] qui ne nous sert plus. Donc on a une vue = une activité. 8.com 26/157 .setClass(this.class). ici une activité de type [SecondActivity] qui sera chargée d'afficher la vue n° 2.5. protected void navigateToView2() { 3. 10. nous supprimons la vue [activity_main.putExtra] sont (Object clé . // on crée un Intent 5. On notera que la méthode [EditText. Cet objet va permettre de préciser et l'activité à lancer et les informations à lui passer .getText(). Créez une configuration d'exécution en suivant ce qui a été fait au paragraphe 2. ligne 5 : créer un objet de type [Intent]. intent.toString().1. Il faut utiliser la méthode [toString] pour avoir le texte saisi. Exécutez-la et vérifiez que le bouton [Vue n° 2] de la vue n°1 vous emmène bien sur la vue n° 2. } Les commentaires décrivent les étapes à réaliser pour le changement de vue : 1. Il faut se souvenir que l'activité [MainActivity] affiche elle la vue n° 1. ici une activité de type [SecondActivity] 11. 2. // naviguer vers la vue n° 2 2. Intent intent = new Intent(). // on lance l'Intent. // on associe cet Intent à une activité 7. SecondActivity.xml] de la façon suivante : http://tahe. 3.5 Construction de la vue n° 2 1 • en [1]. Il nous faudra définir le type [SecondActivity] . Object valeur). intent. startActivity(intent).getText()] qui rend le texte saisi dans la zone de saisie ne rend pas un type [String] mais un type [Editable]. edtNom. 12. ligne 11 : lancer l'activité définie par l'objet [Intent]. ligne 9 : de façon facultative.trim()). // on associe des informations à cet Intent 9. page 12. Les paramètres de [Intent.

android:layout_height="wrap_content" 31. android:layout_height="match_parent" > 5. android:layout_alignParentLeft="true" 11. android:layout_alignParentTop="true" 12. android:layout_width="wrap_content" 30. android:text="@string/textView_bonjour" /> 26. android:layout_alignParentLeft="true" 22. <Button 28. 6. android:layout_marginLeft="88dp" 13. android:text="@string/vue2_titre" 15. <?xml version="1. android:id="@+id/textView_bonjour" 19. android:layout_width="match_parent" 4.com 27/157 . android:id="@+id/textView_titre" 8.xml] est le suivant : 1. android:textSize="50sp" /> 16. android:layout_below="@+id/textView_bonjour" 33. android:layout_below="@+id/textView_titre" 23. android:layout_marginTop="25dp" 34. android:text="@string/btn_vue1" /> 35.0" encoding="utf-8"?> 2. 36. android:layout_marginLeft="45dp" 24.android. android:layout_marginTop="35dp" 25. <TextView 18. 27. </RelativeLayout> http://tahe. android:layout_height="wrap_content" 10. android:id="@+id/button_vue1" 29. android:layout_width="wrap_content" 20. android:layout_width="wrap_content" 9.com/apk/res/android" 3. android:layout_marginTop="26dp" 14. 17.Les composants sont les suivants : N° Id Type 1 textView_titre TextView 2 textView_bonjour TextView 5 button_vue1 Button Rôle Titre de la vue un texte pour passer à la vue n° 1 Le fichier XML [vue2. android:layout_alignLeft="@+id/textView_bonjour" 32. <TextView 7.developpez. <RelativeLayout xmlns:android="http://schemas. android:layout_height="wrap_content" 21.

if (extras != null) { 28.TextView. import android. // on récupère l'intent s'il existe 24. import android. if (nom != null) { http://tahe. Intent intent = getIntent(). 10. nous avons écrit le code suivant : 1. 8. protected void onResume() { 22. // on récupère les composants de la vue 17. 6. intent.toString(). import android. Nous les exploitons maintenant et cela se passe dans le code de [SecondActivity] : On ajoute au code de [SecondActivity] une méthode [onResume].os. 11.id. textViewBonjour = (TextView) findViewById(R.Activity. setContentView(R. @Override 13.vue2).class). C'est donc un endroit pour préparer la vue.getExtras().onCreate(savedInstanceState). String nom = extras. 23. // on associe cet Intent à une activité 7.6 Exploitation des informations de l'Intent de l'activité Dans [MainActivity].content. 30. if (intent != null) { 26. 16. 2.widget.onResume().com 28/157 . 18. 6. // on crée un Intent 5. package istia. nous avons mis pour [SecondActivity] des informations qui n'ont pas été exploitées.android. } 19. @Override 21. 15.Bundle.Exécutez le projet [exemple-02] et vérifiez que vous obtenez bien la nouvelle vue. protected void onCreate(Bundle savedInstanceState) { 14. 3. // on navigue vers la vue n° 2 en lui passant le nom saisi dans la vue n° 1 4. Cette méthode est l'une des méthodes exécutées juste avant l'affichage de la vue. ici une activité de type [SecondActivity] 11. 5. 20.app. 3.developpez. 8. intent. public class SecondActivity extends Activity { 9.Intent. // naviguer vers la vue n° 2 2. SecondActivity. // on lance l'Intent. // on récupère le nom 29. import android. startActivity(intent). 12.st. 4.textView_bonjour).trim()).setClass(this. Bundle extras = intent. // on associe des informations à cet Intent 9. 27. super.getText(). 7.layout.getString("NOM"). 12. super. edtNom. Le code de [SecondActivity] évolue comme suit : 1. 25. protected void navigateToView2() { 3. private TextView textViewBonjour.putExtra("NOM". } Ligne 9. 10. Intent intent = new Intent().

10.id. } 29. } • • • • • • • ligne 17 : on récupère une référence sur le composant [TextView] de la vue n° 2 .. 11. } 34.st. 26. private Button btnVue1. 4. textViewBonjour = (TextView) findViewById(R. } 35.setText(String. Le code de [SecondActivity] évolue comme suit : 1. btnVue1 = (Button) findViewById(R. ligne 32 : on l'affiche. } 36. 28. 8. ligne 26 : la méthode [Intent. public void onClick(View arg0) { 24. @Override 23. 3. Testez cette nouvelle version. }).app. 30. 5. .id. 16. private TextView textViewBonjour. 12. nom)). // les champs de la vue 9.setOnClickListener(new OnClickListener() { 22.31. ligne 24 : la classe [Activity] a une méthode [getIntent] qui rend l'objet [Intent] associé à l'activité .7 Navigation de la vue n° 2 vers la vue n° 1 Pour naviguer de la vue n° 1 à la vue n° 2 nous allons suivre la procédure vue précédemment : • mettre le code de navigation dans l'activité [SecondActivity] qui affiche la vue n° 2 .textView_bonjour). 35. super. • écrire la méthode [onResume] dans l'activité [MainActivity] qui affiche la vue n° 1 . protected void navigateToView1() { http://tahe. // gestionnaire d'évts 20. // on l'affiche 32. // bouton [Vue1] 21.button_vue1).. 2.getExtras] rend un type [Bundle] qui est une sorte de dictionnaire contenant les informations associées à l'objet [Intent] de l'activité . . Tapez un nom dans la vue n° 1 et vérifiez que la vue n° 2 l'affiche bien. btnVue1. } 37. 6. import android. @Override 31. protected void onResume() { 32. textViewBonjour. // on récupère les composants de la vue 17. 19. 33.onCreate(savedInstanceState). 3. 15. navigateToView1(). lignes 21-36 : la méthode [onResume] sera exécutée juste avant l'affichage de la vue n° 2 .com 29/157 . 33. ligne 22 : on doit appeler la méthode [onResume] de la classe parent.Activity.vue2).layout. setContentView(R. protected void onCreate(Bundle savedInstanceState) { 14. package istia. 38. public class SecondActivity extends Activity { 7.android. } 34. } 27. ligne 29 : on récupère le nom placé dans l'objet [Intent] de l'activité . // on passe à la vue n° 2 25.. C'est obligatoire ..format("Bonjour %s !". @Override 13. 18.developpez.

43. edtNom. } 15. 47. ligne 37 : on crée un nouvel [Intent] . extras2. // on l'affiche 13. lignes 35-49 : la méthode [navigateToView1] . 41. ce qui n'était pas le cas jusqu'à maintenant. 44. 40. @Override 2. 49. } 17. 46. lignes 21-27 : on associe la méthode [navigateToView1] au clic sur ce bouton . } Faites ces modifications et testez votre application. // on récupère l'intent s'il existe 5. ligne 40 : on récupère l'Intent associé à [SecondActivity] . MainActivity. if (intent2 != null) { Bundle extras2 = intent2. Dans le code de [MainActivity] on ajoute la méthode [onResume] suivante : 1. } 51. http://tahe. 11.getString("NOM")). } • • • • • • • • • // on crée un Intent pour l'activité [MainActivity] Intent intent1 = new Intent().com 30/157 .onResume().36. if (extras != null) { 9. 50. } ligne 18 : on récupère une référence sur le bouton [Vue n° 1] . ligne 38 : associé à l'activité [MainActivity] . 37. 48. if (nom != null) { 12. 38. String nom = extras. on doit retrouver le nom saisi initialement. 8.setClass(this.getExtras(). 45.developpez. intent1. 4. protected void onResume() { 3. ligne 42 : on récupère les informations de cet Intent .getExtras(). ligne 48 : l'activité [MainActivity] est lancée. if (intent != null) { 7. // on récupère le nom 10.class). ligne 45 : la clé [NOM] est récupérée dans [intent2] pour être mise dans [intent1] avec la même valeur associée . 39.getString("NOM"). 6. if (extras2 != null) { // on met le nom dans l'Intent de [MainActivity] intent1. super. 14. } 16.putExtra("NOM". Bundle extras = intent. } // on lance [MainActivity] startActivity(intent1). Maintenant quand on revient de la vue n° 2 à la vue n° 1.setText(nom). // on récupère l'Intent de l'activité courante [SecondActivity] Intent intent2 = getIntent(). Intent intent = getIntent(). 42.

10] . 3 • en [3]. fixez le dossier d'installation du nouveau projet [exemple-03] . créez un projet Maven . choississez l'archétype [de. 2 • en [2]. procédez comme suit : http://tahe. construire une application architecturée en couches qui communique avec des services distants. Si cet archétype ne vous est pas proposé. Pour les besoins des applications à venir.android. naviguer entre-elles.developpez. gérer les événements de celles-ci .4 Exemple-03 : construire un projet Maven / Android Qu'avons-nous appris jusqu'à maintenant : • • • construire des vues .0.akquinet.com 31/157 . nous allons explorer de nouveaux domaines : • • • construire un projet Android avec Maven construire une vue avec des onglets . 1 • en [1]. C'est suffisant pour bon nombre d'applications. Explorons tout d'abord la création d'un projet Android avec Maven.archetypes / android-quickstart – version 1.

0.0. en [5]. <version>0.org/2001/XMLSchema-instance" 3. <groupId>exemples</groupId> 6. <project. en [7]. donnez les références de l'archétype désiré : 6 7 • • en [6].0 http://maven.apache.apache.0.sourceEncoding>UTF-8</project.xsd"> 4.version> 4. 11. <project xmlns="http://maven.0.version>3.build.sourceEncoding> 13. </properties> 17. <platform.version> 16. <modelVersion>4. <properties> 12. </platform.1-SNAPSHOT</version> 8. xsi:schemaLocation="http://maven.org/POM/4.4 14. <dependency> http://tahe.5 4 • • en [4].0</modelVersion> 5.apache.w3. <artifactId>exemple-03</artifactId> 7.3</android.1.developpez. <android. <?xml version="1.5.0" xmlns:xsi="http://www.plugin. <packaging>apk</packaging> 9.org/POM/4.org/mavenv4_0_0.version> 15. <dependencies> 19.0" encoding="UTF-8"?> 2.xml] généré pour le projet Maven est le suivant : 1. 18.plugin.com 32/157 .build.1. le projet Maven créé. ajoutez un archétype . donnez les caractéristiques du projet Maven . Le fichier [pom. <name>exemple-03</name> 10.

maven. </plugins> 49.generation2</groupId> 41. <sdk> 44. </dependencies> 26. On n'y touchera pas . <pluginManagement> 29. </plugin> 48.android.20. lignes 19-24 : le projet a une dépendance sur l'OS Android. On notera qu'elle est de portée [provided]. <artifactId>android-maven-plugin</artifactId> 33. Créez une configuration pour le projet [exemple-03] et exécutez-la.version}</version> 23.android</groupId> 21.plugins. <groupId>com. </dependency> 25. </plugin> 36.plugins. <finalName>${project. <platform>16</platform> 45.jayway. <version>${android.maven.jayway. http://tahe. </project> • • les lignes 26-49 décrivent le plugin Maven pour Android. <plugins> 39. <plugins> 30. On obtient la vue suivante : Travail à faire : changez le titre de la vue. <version>${platform.google. <extensions>true</extensions> 35.android. <plugin> 40. <artifactId>android-maven-plugin</artifactId> 42.com 33/157 . <groupId>com.generation2</groupId> 32. </build> 50. </configuration> 47. </pluginManagement> 38.version}</version> 34. <build> 27. <artifactId>android</artifactId> 22.plugin. Nous verrons qu'il faut souvent ajouter une autre dépendance aux projets Maven / Android.developpez. ç-à-d que l'OS ne sera pas embarqué dans le binaire du projet. <groupId>com. </plugins> 37. <configuration> 43. <scope>provided</scope> 24. <plugin> 31.artifactId}</finalName> 28. </sdk> 46.

Créez une configuration d'exécution pour le projet [exemple-04] et exécutez-la. Ceci fait. Il s'agit d'enlever les annotations [@Override] sur les gestionnaires d'événements.0</modelVersion> <groupId>exemples</groupId> <artifactId>exemple-04</artifactId> <version>0. Dupliquez le projet [exemple-02] dans [exemple-04] en suivant l'exemple du paragraphe 3. Des erreurs de compilation apparaissent alors dans les codes Java de [MainActivity] et [SecondActivity] : Corrigez-les. C'est intéressant car les projets Maven sont reconnus par tous les IDE (Netbeans.5 Exemple-04 : maveniser un projet Android existant Nous montrons ici comment transformer un projet Android existant en projet Maven.0.1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-04</name> on met [exemple-04] aux lignes 3 et 6. 5.xml] du projet [exemple-03] dans le projet [exemple-04] . Puis modifiez-le de la façon suivante : 1. en [2].developpez. Intellj Idea). copiez le fichier [pom. 6. page 22. 3. transformez le projet [exemple-04] en projet Maven [Propriétés du projet / Configure / Convert to Maven Project].1. 2. • <modelVersion>4. 2 1 • • en [1]. http://tahe.0.com 34/157 . 4. le nouveau projet [exemple-04] . Eclise.

6 Exemple-05 : navigation par onglets
Nous allons maintenant explorer les interfaces à onglets.

6.1

Le projet Android

Créez un projet Android (pas Maven) appelé [exemple-05]. Suivez la démarche du projet [exemple-01] jusqu'à la dernière étape où il
y a un changement :

1

2



3

en [1], donnez les informations pour le nouveau projet ;
en [2], précisez l'API 11 comme API minimale. Ce n'est qu'à partir de cet API que les onglets ont été gérés ;
en [3], dans la dernière étape de l'assistant, précisez que vous voulez une navigation à onglets.

Créez un contexte d'exécution pour le projet [exemple-05] et exécutez-la. Vous obtenez une interface avec trois onglets :
1

2

Apprenons à programmer ces onglets en en ajoutant un autre.
Le projet [Exemple-05] est composé d'une activité [1] et de deux vues [2].

6.2

Les vues

La vue [activity_main.xml] est la suivante :
1. <android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
2.
xmlns:tools="http://schemas.android.com/tools"
3.
android:id="@+id/pager"
4.
android:layout_width="match_parent"
5.
android:layout_height="match_parent"
6. tools:context=".MainActivity" />

Cette vue est un conteneur dans lequel vont venir s'afficher des [Fragments]. On notera deux points :

ligne 1 : le gestionnaire de disposition porte un nom particulier ;

ligne 3 : l'identifiant de ce gestionnaire. Il est utilisé dans le code ;

http://tahe.developpez.com

35/157

La vue [fragment_main_dummy.xml] est la suivante :
1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2.
xmlns:tools="http://schemas.android.com/tools"
3.
android:layout_width="match_parent"
4.
android:layout_height="match_parent"
5.
android:paddingBottom="@dimen/activity_vertical_margin"
6.
android:paddingLeft="@dimen/activity_horizontal_margin"
7.
android:paddingRight="@dimen/activity_horizontal_margin"
8.
android:paddingTop="@dimen/activity_vertical_margin"
9.
tools:context=".MainActivity$DummySectionFragment" >
10.
11.
<TextView
12.
android:id="@+id/section_label"
13.
android:layout_width="wrap_content"
14.
android:layout_height="wrap_content" />
15.
16. </RelativeLayout>

On retrouve là des choses connues :

ligne 1 : un gestionnaire de disposition de type [RelativeLayout] ;

lignes 11-14 : un composant [TextView] nommé [@+id/section_label] ;

6.3

L'activité

Le code généré pour l'activité est assez complexe. C'est une caractéristique de la programmation Android. Tout devient vite assez
complexe.

Le code de [MainActivity] est le suivant :
1. package istia.st.android;
2.
3. import java.util.Locale;
4.
5. ...
6.
7. public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
8.
9.
// le gestionnaire de fragments ou sections
10.
SectionsPagerAdapter mSectionsPagerAdapter;
11.
12.
// le conteneur des fragments
13.
ViewPager mViewPager;
14.
15.
@Override
16.
protected void onCreate(Bundle savedInstanceState) {
17.
// classique
18.
super.onCreate(savedInstanceState);
19.
setContentView(R.layout.activity_main);
20.
21.
// la barre d'onglets
22.
final ActionBar actionBar = getActionBar();
23.
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
24.

http://tahe.developpez.com

36/157

25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.

// instanciation de notre gestionnaire de fragments
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// on récupère la référence du conteneur de fragments
mViewPager = (ViewPager) findViewById(R.id.pager);
// et associé à notre gestionnaire de fragments
mViewPager.setAdapter(mSectionsPagerAdapter);
// on crée autant d'onglets qu'il y a de fragments affichés par le conteneur
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
// actionBar est la barre d'onglets
// actionBar.newTab() crée un nouvel onglet
// actionBar.newTab().setText() donne un titre à cet onglet
// actionBar.newTab().setText().setTabListener(this) indique que cette classe gère
les évts des onglets

39.
actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabLi
stener(this));
40.
}
41.
}
42.
43.
44.
@Override
45.
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
46.
// un onglet a été sélectionné - on change le fragment affiché par le conteneur de
fragments
47.
mViewPager.setCurrentItem(tab.getPosition());
48.
}
49.
50.
@Override
51.
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
52.
}
53.
54.
@Override
55.
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
56.
}
57.
58.
// notre gestionnaire de fragments
59.
// à redéfinir pour chaque application
60.
// doit définir les méthodes suivantes
61.
// getItem, getCount, getPageTitle
62.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
63. ...
64.
}
65.
66.
// un fragment est une vue affichée par un conteneur de fragments
67.
public static class DummySectionFragment extends Fragment {
68. ...
69.
}
70.
71. }




ligne 7 : l'activité dérive de la classe [FragmentActivity]. C'est nouveau. Dans les exemples précédents, elle dérivait de la
classe [Activity]. Alors que la classe [Activity] ne gérait qu'une vue, la classe [FragmentActivity] permet de gérer plusieurs
vues appelées [Fragments] ;
ligne 13 : Android fournit un conteneur de vues de type [android.support.v4.view.ViewPager]. Il faut fournir à
ce conteneur un gestionnaire de vues ou fragments. C'est le développeur qui le fournit ;
ligne 10 : le gestionnaire de fragments utilisé dans cet exemple. Son implémentation est aux lignes 62-64 ;
ligne 16 : la méthode exécutée à la création de l'activité ;
ligne 19 : la vue [activity_main.xml] est associée à l'activité. Cette vue est un conteneur dans lequel vont venir s'afficher des
[Fragments] :

http://tahe.developpez.com

37/157

29.setArguments(args). ligne 29 : on récupère dans la vue [activity_main. 44. 42. Le paramètre du constructeur est la classe Android [android. 25.developpez. } // doit rendre le fragment n° i avec ses éventuels arguments @Override public Fragment getItem(int position) { // création du fragment et donc d'une vue Fragment fragment = new DummySectionFragment().v4. position + 1). 37.1. 18. getCount. 41. // 2.com/apk/res/android" 2. 35. 39. } } http://tahe. 43. 23. 6. 30.app. 27.toUpperCase(l). 34. tools:context=". 33. 7.title_section2). 9. } // rend le titre du fragment n° position @Override public CharSequence getPageTitle(int position) { Locale l = Locale. 36. 38. notre gestionnaire de fragments // à redéfinir pour chaque application // doit définir les méthodes suivantes // getItem. 19. // les arguments du fragment .view. 14. 8.support. ligne 26 : le gestionnaire de fragments est instancié. 26. } // rend le nombre de fragments à gérer @Override public int getCount() { // 3 fragments return 3. case 2: return getString(R. 11.ViewPager xmlns:android="http://schemas.getDefault().string. 32. 3.com 38/157 . 46.support. Le gestionnaire de fragments [SectionsPagerAdapter] est le suivant : 1. 24.ici [position+1] Bundle args = new Bundle(). 17. case 1: return getString(R. 22. 20. args. 5. <android. 12.MainActivity" /> • • • Les fragments vont venir s'insérer dans le conteneur d'id [pager] de la ligne 3.putInt(DummySectionFragment. xmlns:tools="http://schemas. fragment.string. 4. 16. 31. getPageTitle public class SectionsPagerAdapter extends FragmentPagerAdapter { // constructeur public SectionsPagerAdapter(FragmentManager fm) { super(fm). ligne 31 : le gestionnaire de fragments est lié au conteneur de fragments .android.ARG_SECTION_NUMBER.title_section1).android. } return null. 28. 40.string.FragmentManager] .title_section3).com/tools" 3. android:layout_width="match_parent" 5. 15.toUpperCase(l).toUpperCase(l). 13. // on rend le fragment return fragment. 21. android:id="@+id/pager" 4. switch (position) { case 0: return getString(R.xml] la référence du conteneur de fragments . 10. 45.v4. android:layout_height="match_parent" 6.

for (int i = 0.toString(getArguments().xml] est associée au fragment. Le constructeur nous est imposé. On peut les utiliser avec des vues sans onglets. Pour l'instant. lignes 27-30 : getCount rend le nombre de fragments gérés. // on retourne la vue créée 18. Les notions de fragments et de conteneur de fragments sont indépendantes des onglets. ligne 18 : un [Bundle] est créé. un entier associé à la clé [DummySectionFragment. 5.toString(getArguments(). ligne 22 : le fragment est rendu .section_label). 4. return rootView.newTab().getInt(ARG_SECTION_NUMBER))] récupère cet entier.ARG_SECTION_NUMBER] .xml] : 1. false). nous n'avons pas parlé des onglets à dessein. public static class DummySectionFragment extends Fragment { 3. <string name="title_section3">Section 3</string> Les trois fragments créés sont du type [DummySectionFragment] suivant : 1. ligne 34 : les titres des trois fragments sont trouvés dans le fichier [strings.setText() donne un titre à cet onglet 6. 17. le fragment a reçu des arguments. 9. // on crée autant d'onglets qu'il y a de fragments affichés par le conteneur 2. Celui-ci est ensuite donné comme texte du [TextView] .layout. • ligne 16 : on récupère les arguments du fragment. • ligne 12 : la vue [fragment_main_dummy. Cette méthode doir rendre la vue associée au fragment.getInt(ARG_SECTION_NUMBER))). // le fragment est associé à la vue [fragment_main_dummy] 12.com 39/157 .inflate(R. @Override 10. // on récupère une référence sur le [TextView] 14. <string name="title_section2">Section 2</string> 3. dummyTextView. // actionBar. ligne 16 : le fragment est instancié. View rootView = inflater. ViewGroup container. • Fragment getItem(i) : rend le fragment n° i.app.developpez. public DummySectionFragment() { 7. 6. // un fragment est une vue affichée par un conteneur de fragments 2. Nous devons définir trois méthodes : • int getCount() : rend le nombre de fragments à gérer. Bundle savedInstanceState) { 11. • ligne 14 : on récupère une référence sur ce [TextView] . La gestion des onglets est faite aux lignes suivantes : 1.• • • • • • • • • ligne 5 : le gestionnaire de fragments étend la classe Android [android. public View onCreateView(LayoutInflater inflater. // actionBar. // actionBar. i < mSectionsPagerAdapter. 15.setTabListener(this) indique que cette classe gère les évts des onglets http://tahe. • CharSequence getPageTitle(i) : rend le titre du fragment n° i .id. ligne 14 : getItem(i) rend le fragment n° i. ici trois .setText(). Nous avons vu que cette vue n'avait qu'un seul composant.v4. C'est une sorte de dictionnaire (clé.newTab().ARG_SECTION_NUMBER]. • ligne 18 : on rend la vue ainsi créée .setText(Integer.FragmentManager]. 19. } 20. On se rappelle que lorsqu'il a été créé. // on lui affecte un texte 16. Le code [Integer.fragment_main_dummy. ligne 19 : la valeur (i+1) est associée à la clé [DummySectionFragment. TextView dummyTextView = (TextView) rootView. public static final String ARG_SECTION_NUMBER = "section_number". ligne 20 : le [Bundle] est passé au fragment . On peut lui transmettre des informations via un type [Bundle] . un [TextView] . Ici tous les fragments seront identiques de type [DummySectionFragment] . } Un fragment doit définir la méthode [onCreateView] de la ligne 10. 13. i++) { 3.support. valeur) .newTab() crée un nouvel onglet 5.findViewById(R. container.getCount(). // actionBar est la barre d'onglets 4. } 8. <string name="title_section1">Section 1</string> 2.

actionBar. <resources> 3. mViewPager. public void onTabReselected(ActionBar. // un onglet a été sélectionné . 2 1 • • en [1]. } On notera ligne 15. la façon de changer de fragment dans le conteneur de fragments.setText(mSectionsPagerAdapter. 6.developpez.Tab tab. 4. <?xml version="1.xml] du projet [exemple-01] dans le projet [exemple-05]. Tout d'abord copions la vue [vue1.7.Tab tab. @Override 23. public void onTabUnselected(ActionBar. 12. @Override 19. en [2].addTab(actionBar. public void onTabSelected(ActionBar.getPageTitle(i)). @Override 13. On rajoute les textes manquants en les prenant dans le fichier [res/values/strings.xml] du projet [exemple-01] : 1. <string name="app_name">Exemple-05</string> 5. Créez un environnement de configuration pour le projet [exemple-05] et exécutez-le.com 40/157 .Tab tab. 22. ces erreurs proviennent de textes absents dans le fichier [res/values/strings. 11. FragmentTransaction fragmentTransaction) { 24. FragmentTransaction fragmentTransaction) { 20. } 10.setCurrentItem(tab. } 17.getPosition()). } 21.newTab().xml] . 16.0" encoding="utf-8"?> 2. 8. <string name="title_section1">Section 1</string> 7. la vue [vue1. 18. <string name="title_section2">Section 2</string> http://tahe.xml] présente des erreurs . FragmentTransaction fragmentTransaction) { 14. } 9. <string name="action_settings">Settings</string> 6.setTabLi stener(this)).4 Un nouveau fragment Apprenons à créer un fragment et à l'afficher.on change le fragment affiché par le conteneur de fragments 15.

nous nous inspirons de la classe [DummySectionFragment] étudiée précédemment : 1. 41.. <string name="title_section3">Section 3</string> 9. doValider().inflate(R. 8.8. 17. 39. @Override 14.button_vue2). } 40.findViewById(R.vue1. <string name="btn_Valider">Validez</string> 12. public class Vue1Fragment extends Fragment { 7.id. private Button btnVue2. 21. btnVue2. btnValider = (Button) rootView. return rootView.setOnClickListener(new OnClickListener() { 31.st. public View onCreateView(LayoutInflater inflater. Bundle savedInstanceState) { 15. public void onClick(View arg0) { 26. public void onClick(View arg0) { 33..editText_nom). 14. @Override 32. // les champs de la vue affichée par le fragment 9. 10. // un fragment est une vue affichée par un conteneur de fragments 6. // le fragment est associé à la vue [vue1] 16.android. } 36. 4. private void navigateToView2() { http://tahe. private Button btnValider. btnValider. <string name="vue1_titre">Vue n° 1</string> 10. edtNom = (EditText) rootView.findViewById(R. 5. 2. <string name="textView_nom">Quel est votre nom :</string> 11.setOnClickListener(new OnClickListener() { 24. private EditText edtNom. 29.layout. 35.xml] : Pour créer la classe. 27. false).id. @Override 25. } 28. // on récupère les composants de la vue 18. // bouton [Vue2] 30. navigateToView2(). }).com 41/157 . 12. . container. View rootView = inflater. }).button_valider). nous créons la classe [Vue1Fragment] qui va être le fragment chargé d'afficher la vue [vue1. // on passe à la vue n° 2 34. 20. 11.developpez. // gestionnaire d'évts 22. 19. ViewGroup container. </resources> Maintenant.findViewById(R. 3. package istia. 37. // bouton [Valider] 23. // on retourne la vue créée 38.id. btnVue2 = (Button) rootView. <string name="btn_vue2">Vue n° 2</string> 13. 13.

43. lignes 41-44 : nous n'implémentons pas la navigation vers la vue [Vue n°2]. // TODO } protected void doValider() { // on affiche le nom saisi Toast. le reste est repris du projet [exemple-01] . nous n'avons qu'une activité qui affiche différentes vues ou fragments .getText(). Exécutez l'environnement de configuration du projet [exemple-05].getActivity()] permet d'avoir l'activité dans laquelle se trouve le fragment. C'est dans cette méthode qu'on associe une vue au fragment . on obtient la vue suivante : http://tahe.show().makeText(getActivity(). 48. on obtient la vue suivante : Si nous tapons un nom et que nous validons. } 50. 49.format("Bonjour %s". 46.makeText] est de type [Activity]. ç-à-d de [vue1. 44.com 42/157 .toString()). Il s'agit de [MainActivity] puisque dans cette architecture.xml] est associée au fragment . edtNom.xml] . lignes 18-20 : on récupère les composants de la vue [rootView]. ligne 48 : le premier paramètre de [Toast. La méthode [Fragment. ligne 16 : la vue [vue1. 45. String. 47.LENGTH_LONG). Toast. Nous allons le faire ultérieurement avec le conteneur de fragments .developpez. } • • • • • • ligne 14 : la méthode [onCreateView] doit être implémentée par le fragment.42. Vous devez obtenir la vue suivante : Si on clique sur l'onglet [VUE N° 1].

Maintenant passez sur un autre onglet et revenez sur l'onglet [VUE N° 1]. On retrouve ainsi le fragment dans l'état où on l'a laissé.com 43/157 . On voudra passer d'une vue à une autre seulement si certaines conditions sont remplies. le nom saisi a été conservé alors que dans le projet [exemple-02] il disparaissait lorsqu'on naviguait vers une autre vue et qu'on revenait. Dans les applications que nous allons écrire. On obtient la vue suivante : Ci-dessus. 6. ce comportement ne sera pas souhaitable.5 Désactiver le Swipe ou Balayage Dans l'application précédente. Revenons sur la vue XML principale [activity_main] : http://tahe.developpez. le conteneur de fragments garde les fragments en mémoire et ne les régénère pas de nouveau lorsqu'ils doivent être réaffichés. lorsque vous balayez la tablette avec la main vers la gauche ou la droite. En fait. Nous allons apprendre à désactiver le balayage des vues (swipe). la vue courante laisse alors place à la vue de droite ou de gauche selon les cas.

2.ViewPager xmlns:android="http://schemas. Pour désactiver le balayage. 2.developpez. } 16. 3.android. <android.Le code XML de la vue est le suivant : 1. SectionsPagerAdapter mSectionsPagerAdapter. Ligne 10. 6. package istia.android. 7.Context. ViewPager mViewPager.ViewPager.util. // le gestionnaire de fragments ou sections 7. 4. import android. import android.TabListener { 5.view. android:layout_height="match_parent" 6.support. tools:context=".view. boolean isSwipeEnabled. 10..com/tools" 3.com/apk/res/android" 2.android. public class MainActivity extends FragmentActivity implements ActionBar. . super(context). 4. import android.ViewPager.content.view. On retrouve cette classe dans l'activité [MainActivity] : 1. 12. 15.view.support.ViewPager] (ligne 1).view. import android. 5. 6.v4. android:id="@+id/pager" 4. 3.v4. public class MyPager extends ViewPager { 9.com 44/157 . import android. le gestionnaire de pages est de type [android. // contrôle le swipe 11. 8. // le conteneur des fragments 10. 8.v4.support.st. http://tahe. on est amené à dériver cette classe de la façon suivante : 1.support. android:layout_width="match_parent" 5. 9.v4. xmlns:tools="http://schemas.MainActivity" /> La ligne 1 désigne la classe qui gère les pages de l'activité. public MyPager(Context context) { 14. 13.MotionEvent..AttributeSet.

android.layout. 36. 3.onInterceptTouchEvent(event).MainActivity" /> Ligne 1. 8. android:layout_width="match_parent" 5. 22. 21. 33. ligne 11 : le booléen qui sert à indiquer si on accepte ou non le balayage de la main.onCreate(savedInstanceState). 42. setContentView(R. MyPager mViewPager. Ceci fait. Cela se fait dans la vue XML [activity_main] et dans l'activité principale [MainActivity]. 23. 44. 31. 26. on utilise la nouvelle classe. public class MainActivity extends FragmentActivity implements ActionBar. protected void onCreate(Bundle savedInstanceState) { 11. 9. 39.onTouchEvent(event). <istia. } } // setter public void setSwipeEnabled(boolean isSwipeEnabled) { this. 29. 34. tools:context=". } • • • ligne 8 : la classe [MyPager] étend la classe [ViewPager] . // le gestionnaire de fragments ou sections 4. Dans [activity_main] on écrit : 1. AttributeSet attrs) { super(context.com 45/157 .developpez. 30. 43. @Override 10. 28. } } @Override public boolean onTouchEvent(MotionEvent event) { // swipe autorisé ? if (isSwipeEnabled) { return super. xmlns:tools="http://schemas. 37. 24. public MyPager(Context context. // classique 12.com/tools" 3.st.isSwipeEnabled = isSwipeEnabled. 38. } else { return false. // le conteneur des fragments 7. 18. attrs). 35.com/apk/res/android" 2. 25. } else { return false. 32. SectionsPagerAdapter mSectionsPagerAdapter.android. super. // la barre d'onglets http://tahe.activity_main). Il leur suffit de rendre le booléen [false] pour inhiber le balayage . 15. android:layout_height="match_parent" 6. 40. 41. } @Override public boolean onInterceptTouchEvent(MotionEvent event) { // swipe autorisé ? if (isSwipeEnabled) { return super.17. 13. } 46. il faut utiliser désormais notre nouveau gestionnaire de pages. Dans [MainActivity]. 20. 5. 19. le code évolue comme suit : 1. 6.TabListener { 2. Elles rendent toutes deux un booléen. android:id="@+id/pager" 4. 27. sur un balayage de la main. 45.android.MyPager xmlns:android="http://schemas. 14. les gestionnaires d'événements des lignes 22 et 32 peuvent être appelés.

• • final ActionBar actionBar = getActionBar().NAVIGATION_MODE_TABS). 27. 25. 23. // instanciation de notre gestionnaire de fragments mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()).developpez. Dans toutes les applications à venir..pager). // on inhibe le swipe mViewPager.setNavigationMode(ActionBar. // on crée autant d'onglets qu'il y a de fragments affichés par le conteneur ligne 7 : le gestionnaire de pages a le type [MyPager] . // on récupère la référence du conteneur de fragments mViewPager = (MyPager) findViewById(R. 18. 20.16. 24. le balayage sera inhibé.id. 30.setSwipeEnabled(false). // il est associé à notre gestionnaire de fragments mViewPager. Nous ne le rappellerons pas. 26.. actionBar. 19. ligne 27 : on inhibe ou non le balayage de la main.setAdapter(mSectionsPagerAdapter). 29. http://tahe. Inhibez ou non le balayage et constatez la différence de comportement des vues.com 46/157 . . 28. 21. Testez cette nouvelle version. 22. 17.

7 Exemple-06 : La navigation entre vues revisitée Dans le projet [exemple-02] nous avons introduit la navigation entre vues. Nous nous proposons ici d'avoir 1 activité avec plusieurs vues.developpez. en [3]. • • • • Nous allons ajouter la vue [VUE N° 2] comme nouveau fragment et apprendre comment naviguer de la vue n° 1 à la vue n° 2 et vice-versa.xml] de [exemple-06] des chaînes provenant du fichier [res/values/strings. Pour les corriger. on supprime la vue [fragment_main.com 47/157 . en [2].dummy.xml] de [exemple-02] : <string name="vue2_titre">Vue n° 2</string> <string name="btn_vue1">Vue n° 1</string> <string name="textView_bonjour">"Bonjour "</string> http://tahe. 1 2 En [1] ci-dessus. apparaissent des erreurs de compilation.xml] à partir du projet [exemple-02].1 Le projet Dupliquons le projet [exemple-05] dans [exemple-06] comme il a été fait pour le projet [exemple-02] : 4 1 2 3 en [1]. Nous avons des erreurs [2]. nous copions la vue [vue2. en [4]. il suffit d'ajouter au fichier [res/values/strings. On les ignore pour l'instant. L'activité sera de type [FragmentActivity] et la vue de type [Fragment]. 7.xml] qui ne sert plus . les vues de ce projet . le projet [exemple-06] . Il s'agissait alors d'une navigation entre activités : 1 vue = 1 activité.

9. // le fragment est associé à la vue [vue2] 16. @Override 14. return rootView. // on retourne la vue créée 30. private TextView textViewBonjour. ViewGroup container. private void navigateToView1() { 34.st.id. 4.id. false). Nous créons maintenant le fragment [Vue2Fragment] qui va afficher [vue2. container. 5. import java. 6. nous n'avons plus d'erreurs sur la vue [vue2. 5. // gestionnaire d'évts 21. 27.button_vue1).Bundle. import android. package istia. View rootView = inflater.. // bouton [Vue1] 22. package istia. 33. 13. private Button btnVue1. .inflate(R. public View onCreateView(LayoutInflater inflater. } 28. public class Vue2Fragment extends Fragment { 8. 17.developpez. // on passe à la vue n° 2 26. } 32.os.vue2. 11. 2. // les champs de la vue 10.setOnClickListener(new OnClickListener() { 23. // TODO 35. 2. 29.app.Ceci fait. 12.st.Locale. textViewBonjour = (TextView) rootView. 3. btnVue1 = (Button) rootView. 31. @Override 24.textView_bonjour). navigateToView1().android. import android. // un fragment est une vue affichée par un conteneur de fragments 7.. 19. 36. // on récupère les composants de la vue 18. }). } L'activité [MainActivity] évolue de la façon suivante : 1.android.ActionBar.layout.util.findViewById(R.xml]. 3. 20.findViewById(R.xml] : Son code sera le suivant : 1. } 37. http://tahe.com 48/157 . public void onClick(View arg0) { 25. 38. Bundle savedInstanceState) { 15. btnVue1.. 4.

// classique 19.com 49/157 . } 47. 21. public CharSequence getPageTitle(int position) { 65. public class SectionsPagerAdapter extends FragmentPagerAdapter { 39. 24. setContentView(R. 10. return 2. 13. 25. return getString(R. mViewPager = (MyPager) findViewById(R. return fragments[position]. // notre gestionnaire de fragments 35. 22. // instanciation de notre gestionnaire de fragments 23. // rend le titre du fragment n° position 63.pager). mViewPager. 43. new Vue2Fragment() }.id. 15. @Override 64. 12. // à redéfinir pour chaque application 36.getDefault(). // doit rendre le fragment n° i avec ses éventuels arguments 49. public class MainActivity extends FragmentActivity{ 9. 27.activity_main).setSwipeEnabled(false). // constructeur 44. 48. super(fm). mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()). // on inhibe le swipe 30. mViewPager. // doit définir les méthodes suivantes 37. 8. // les fragments 41.onCreate(savedInstanceState).toUpperCase(l). Locale l = Locale. } 32.. switch (position) { 67. 62. . 66. http://tahe. Fragment[] fragments = { new Vue1Fragment(). 16.developpez. 29. public Fragment getItem(int position) { 51. case 0: 68. @Override 57. // le conteneur des fragments 14. // getItem. public SectionsPagerAdapter(FragmentManager fm) { 45. // 2 fragments 59. 31. SectionsPagerAdapter mSectionsPagerAdapter. 53.setAdapter(mSectionsPagerAdapter).. super.6. public int getCount() { 58.string. } 54. 46. getPageTitle 38. 40. // on récupère la référence du conteneur de fragments 26. } 61. 42.layout. getCount. @Override 50.vue1_titre). protected void onCreate(Bundle savedInstanceState) { 18. // il est associé à notre gestionnaire de fragments 28. 33. @Override 17. // le gestionnaire de fragments ou sections 11. 20. 7. // on rend le fragment 52. MyPager mViewPager. 55. 60. // rend le nombre de fragments à gérer 56. 34.

st. . public class MainActivity extends FragmentActivity { 7.setCurrentItem(i). .string. Pour nous c'est la vue [Vue n° 1]. return null. public void navigateToView(int i) { 19. 21. Le premier fragment est affiché. 9.69. return getString(R. } http://tahe. 73.. 8.util. ligne 38 : notre gestionnaire de fragments . 11. // navigation 18. } 74. 12. 3. 15.toUpperCase(l). case 1: 70. 7. } 72. . ligne 41 : les deux fragments sont dans un tableau ..vue2_titre).. import java.com 50/157 .developpez. Aussi l'utiliserons-nous également pour passer de l'information d'un fragment à un autre. lignes 57-60 : il y a deux fragments . L'activité unique du projet est une instance de cette classe. Créez une configuration d'exécution pour le projet [exemple-06] puis exécutez-la. 6.2 Mise en place de la navigation C'est la classe [MainActivity] qui va assurer la navigation. protected void onCreate(Bundle savedInstanceState) { 14.Locale. Le code de [MainActivity] évolue comme suit : 1. // on affiche le fragment n° i 20.. lignes 23-30 : création du conteneur de fragments . 71. private String nom. mViewPager. 5. Cette activité est accessible à tous les fragments. } • • • • • • • il n'y a plus d'onglets donc plus de gestion d'onglets .. // informations échangées entre les vues 10.. lignes 50-53 : on rend le fragment n° [position] dans le tableau . 2. 17. } 75. ligne 8 : l'activité étend la classe [FragmentActivity] .android. } 16. @Override 13. 76. 4. package istia.

7. les fragments utiliseront cette méthode de l'activité.. Ici on mémorisera le nom saisie dans la vue 1 . Aussi ces méthodes [onStart. public void setNom(String nom) { 29. return nom. 30. lignes 18-20 : la navigation sera implémentée également dans l'activité. 4. 6. private void navigateToView2() { 2. // on navigue vers la vue 1 3. activité. public String getNom() { 25. textViewBonjour. } 36. http://tahe.nom = nom.getNom())).com 51/157 . this. } 8. if (visible) { 5. @Override 2. } Lorsque la vue n° 2 s'affiche. } 31. public class SectionsPagerAdapter extends FragmentPagerAdapter { 34. 37. 23.getText(). public void setMenuVisibility(final boolean visible) { 3.22.developpez.format("Bonjour %s !". // on mémorise le nom dans l'activité 3. } On a l'équivalent dans [Vue2Fragment] : 1. 28. // on navigue vers la vue 2 5. } 27.navigateToView(1). // getters et setters 24. activité.setNom(edtNom. activité..on affiche le nom saisi dans la vue 1 6. Nous avons désormais les bases pour construire des architectures plus complexes que les précédentes. activité. // la vue est visible .navigateToView(0). elle peut servir d'entrepôt pour la communication entre fragments / vues. La navigation dans [Vue1Fragment] est alors la suivante : 1. } • • ligne 10 : comme l'activité est unique et partagée entre tous les fragments. super. il faut afficher le nom saisi dans la vue n° 1.3 Conclusion A ce point. Pour naviguer.. 7. Il nous faut un événement qui nous dise que le fragment est devenu visible. private void navigateToView1() { 2. // notre gestionnaire de fragments 33. 35. } Exécutez de nouveau le projet [exemple-06] et vérifiez son bon fonctionnement. lors de la création initiale du fragment. onResume] ne sont-elles appelées qu'une fois.setText(String. . Un fragment n'est créé qu'une fois. 26.setMenuVisibility(visible). C'est l'événement suivant : 1. 4. nous avons un début d'architecture cohérent pour une application à plusieurs vues : • une seule activité gère toutes les vues . 32. 4. • cette activité gère la navigation et la transmission d'information entre vues.toString()).

developpez. en [2].1. nous ne gardons que l'activité [MainActivity] et supprimons les deux fragments. en [3].8 Exemple-07 : une architecture à deux couches Nous allons construire une application à une vue et ayant l'architecture suivante : Vue Utilisateur 8.1. on supprime les deux vues associées aux deux fragments. La vue [vue_01] Nous allons créer la vue [vue_01] qui permettra de générer des nombres aléatoires : http://tahe. Cela fait apparaître des erreurs dans [MainActivity] .1 Activité Couche [metier] Le projet Android Nous dupliquons le projet [exemple-06] dans [exemple-07] : 3 1 • • • 8.2 2 en [1].com 52/157 . le projet [exemple-07] .

<TextView http://tahe. Son code XML est le suivant : 1. On voit d'abord le dernier généré . android:layout_height="match_parent" 7. <?xml version="1. <TextView 10.com/tools" 4. android:id="@+id/txt_Titre2" 11.b] valeur de a valeur de b lance la génération des nombres liste des nombres générés dans l'ordre inverse de leur génération.com/apk/res/android" 3. xmlns:tools="http://schemas. 17. android:layout_height="wrap_content" 13. 9.com 53/157 .Ses composants sont les suivants : N° Id 1 edtNbAleas 2 edtA 2 edtB 4 btnExécuter 5 ListView Type EditText EditText EditText Button lstReponses Rôle nombre de nombres aléatoires à générer dans l'intervalle entier [a. android:layout_width="match_parent" 6. android:text="@string/aleas" 15. android:layout_marginTop="20dp" 14. android:orientation="vertical" > 8. android:textAppearance="?android:attr/textAppearanceLarge" /> 16.android. android:layout_width="wrap_content" 12. <RelativeLayout xmlns:android="http://schemas.android. android:id="@+id/RelativeLayout1" 5.developpez.0" encoding="utf-8"?> 2.

31. 40. 41. 55. 39. 30. 51.developpez. 67. 65. 78. 62. 19. android:id="@+id/txt_nbaleas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/txt_Titre2" android:layout_marginTop="20dp" android:text="@string/txt_nbaleas" /> <EditText android:id="@+id/edt_nbaleas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/txt_nbaleas" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/txt_nbaleas" android:inputType="number" /> <TextView android:id="@+id/txt_errorNbAleas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/edt_nbaleas" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/edt_nbaleas" android:text="@string/txt_errorNbAleas" android:textColor="@color/red" /> <TextView android:id="@+id/txt_a" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/txt_nbaleas" android:layout_marginTop="20dp" android:text="@string/txt_a" /> <EditText android:id="@+id/edt_a" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/txt_a" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/txt_a" android:inputType="number" /> <TextView android:id="@+id/txt_b" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/txt_a" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/edt_a" android:text="@string/txt_b" /> <EditText android:id="@+id/edt_b" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/txt_a" android:layout_marginLeft="20dp" android:layout_toRightOf="@+id/txt_b" android:inputType="number" /> <TextView android:id="@+id/txt_errorIntervalle" http://tahe. 21. 25. 34. 52. 45. 60. 28. 33. 61. 29.18. 22. 68. 24. 70. 47. 80. 27. 54. 43. 20. 74. 56. 63. 48.com 54/157 . 23. 53. 58. 46. 64. 59. 42. 79. 77. 35. 72. 66. 57. 37. 76. 69. 38. 26. 71. 49. 32. 36. 73. 50. 75. 44.

81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.

8.1.3

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/edt_b"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/edt_b"
android:text="@string/txt_errorIntervalle"
android:textColor="@color/red" />
<Button
android:id="@+id/btn_Executer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_a"
android:layout_marginTop="20dp"
android:text="@string/btn_executer" />
<TextView
android:id="@+id/txt_Reponses"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_Executer"
android:layout_marginTop="30dp"
android:text="@string/list_reponses"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/blue" />
<ListView
android:id="@+id/lst_reponses"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_Reponses"
android:layout_marginTop="40dp"
android:background="@color/wheat"
android:clickable="true"
tools:listitem="@android:layout/simple_list_item_1" >
</ListView>

</RelativeLayout>

La vue [activity_main]
1
2

La vue XML [activity_main] utilise la classe [istia.st.android.activity.MyPager] [2] aussi doit-elle être écrite de la façon suivante :
1. <istia.st.android.activity.MyPager
xmlns:android="http://schemas.android.com/apk/res/android"
2.
xmlns:tools="http://schemas.android.com/tools"
3.
android:id="@+id/pager"
4.
android:layout_width="match_parent"

http://tahe.developpez.com

55/157

5.
android:layout_height="match_parent"
6. tools:context=".MainActivity" />

Ligne 1, il faut utiliser le package exact de la classe [MyPager] sinon une exception sera lancée.

8.1.4

La couche [métier]

La couche [métier] présente l'interface [IMetier] suivante :
1.
2.
3.
4.
5.
6.
7.
8.

package istia.st.android.metier;
import java.util.List;
public interface IMetier {
public List<Object> getAleas(int a, int b, int n);
}

La méthode [getAleas(a,b,n)] renvoie normalement n nombres entiers aléatoires dans l'intervalle [a,b]. On a prévu également qu'elle
renvoie une fois sur trois une exception, exception également insérée dans les réponses rendues par la méthode. Au final celle-ci
rend une liste d'objets de type [Exception] ou [Integer].
L'implémentation [Metier] de cette interface est la suivante :
1. package istia.st.android.metier;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5. import java.util.Random;
6.
7. public class Metier implements IMetier {
8.
9.
@Override
10.
public List<Object> getAleas(int a, int b, int n) {
11.
// la liste des objets
12.
List<Object> réponses = new ArrayList<Object>();
13.
// qqs vérifications
14.
if (n < 1) {
15.
réponses.add(new AleaException("Le nombre d'entier aléatoires demandé doit être
supérieur ou égal à 1"));
16.
}
17.
if (a < 0) {
18.
réponses.add(new AleaException("Le nombre a de l'intervalle [a,b] doit être
supérieur à 0"));
19.
}
20.
if (b < 0) {
21.
réponses.add(new AleaException("Le nombre b de l'intervalle [a,b] doit être
supérieur à 0"));
22.
}
23.
if (a >= b) {

http://tahe.developpez.com

56/157

24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
}
45. }

réponses.add(new AleaException("Dans l'intervalle [a,b], on doit avoir a< b"));
}
// erreur ?
if (réponses.size() != 0) {
return réponses;
}
// on génère les nombres aléatoires
Random random = new Random();
for (int i = 0; i < n; i++) {
// on génère une exception aléatoire 1 fois / 3
int nombre = random.nextInt(3);
if (nombre == 0) {
réponses.add(new AleaException("Exception aléatoire"));
} else {
// sinon on rend un nombre aléatoire entre deux bornes [a,b]
réponses.add(Integer.valueOf(a + random.nextInt(b - a + 1)));
}
}
// résultat
return réponses;

Le type [AleaException] utilisé par la classe [Metier] est la suivante :
1. package istia.st.android.metier;
2.
3. public class AleaException extends RuntimeException {
4.
5.
public AleaException() {
6.
}
7.
8.
public AleaException(String detailMessage) {
9.
super(detailMessage);
10.
}
11.
12.
public AleaException(Throwable throwable) {
13.
super(throwable);
14.
}
15.
16.
public AleaException(String detailMessage, Throwable throwable) {
17.
super(detailMessage, throwable);
18.
}
19.
20. }

8.1.5

L'activité [MainActivity]

L'activité unique du projet est une instance de la classe [MainActivity] suivante :
1. package istia.st.android.activity;
2.

http://tahe.developpez.com

57/157

3.setAdapter(mSectionsPagerAdapter). // à redéfinir pour chaque application 49.st. 11. 35. mViewPager. 5. 26. 55. // getters et setters 39. @Override 63. // instanciation couche [métier] 34. protected void onCreate(Bundle savedInstanceState) { 19. // il est associé à notre gestionnaire de fragments 29. private IMetier métier. // doit définir les méthodes suivantes 50. public IMetier getMétier() { 40. 56. 4. 59. // getItem. 14. // le gestionnaire de fragments ou sections 9. // notre gestionnaire de fragments 48. 10. // les fragments 54. 22. public class MainActivity extends FragmentActivity { 7.R. Fragment[] fragments = { new Vue_01() }. this.onCreate(savedInstanceState). métier = new Metier(). import istia. // on inhibe le swipe 31. // on rend le fragment 65. 32. 8. super(fm). // couche [métier] 15. return fragments[position]. 61.. } 42. SectionsPagerAdapter mSectionsPagerAdapter.layout. 30.id. getCount. // constructeur 57. 17. setContentView(R. 53. http://tahe. 45. mViewPager = (MyPager) findViewById(R. 13. } 60.setSwipeEnabled(false). 43.activity_main). 25.com 58/157 . 6. 28.métier = métier. public SectionsPagerAdapter(FragmentManager fm) { 58. // instanciation de notre gestionnaire de fragments 24. // doit rendre le fragment n° i avec ses éventuels arguments 62. // on récupère la référence du conteneur de fragments 27. // le conteneur des fragments 12. . return métier. public class SectionsPagerAdapter extends FragmentPagerAdapter { 52.. 37. public void setMétier(IMetier métier) { 44. @Override 18.android. } 46. 38.pager). 21. getPageTitle 51. 47. MyPager mViewPager. mViewPager.developpez. // classique 20. 16. 33. mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()). 41. } 36. super. 23. public Fragment getItem(int position) { 64.

1. 76. 80. private int nbAleas. 17. private TextView txtErrorAleas. switch (position) { case 0: return getString(R.vues. 70.6 } // rend le nombre de fragments à gérer @Override public int getCount() { // 1 fragment return 1. 11. 15. // les éléments de l'interface visuelle 9. private EditText edtNbAleas. ligne 15 : nous avons dit que l'activité était un bon endroit pour partager des données entre vues. private Button btnExecuter.R. } • • • • • • 8. 14. } 86. private ListView listRéponses. Celle-ci ne sera instanciée qu'une fois . ligne 51 : notre gestionnaire de fragments. 77. private int a. Ici nous n'en avons qu'un .getDefault(). public class Vue_01 extends Fragment { 7. } return null. private TextView txtErrorIntervalle. 73. 12.66. 68. 74. 6. 2. 72. ligne 72 : le nombre de fragments .string. 75. Son code est le suivant : 1.android. 84. 82. ligne 34 : instanciation de la couche [métier] . } // rend le titre du fragment n° position @Override public CharSequence getPageTitle(int position) { Locale l = Locale. 78. 83. // les saisies 18. 5. 3.app_name). ligne 54 : le tableau des fragments est réduit au seul fragment [Vue_01] associé à la vue XML [vue_01] . 16. 71. private EditText edtB. 69.toUpperCase(l). 67. } ligne 6 : l'activité est de type [FragmentActivity] . 81. Le fragment de la vue [vue_01] La classe [Vue_01] est l'unique fragment affiché par l'activité du projet. http://tahe.android. 13. 10. 87... 4. import istia. 19.st. package istia.st. 85. 79. On va y stocker une référence sur la couche [métier]. 8. private EditText edtA.developpez.com 59/157 . .

38. btnExecuter. } 46. listRéponses = (ListView) view.findViewById(R.lst_reponses).setOnClickListener(new OnClickListener() { 42. // zones de saisie 31.id. 24. // on note l'activité 27. protected void doExecuter() { 55. La méthode [doExecuter] est la suivante : 1. // on efface les éventuels msg d'erreur précédents 3.id. lignes 41-48 : on définit un gestionnaire d'événements pour le bouton [Exécuter] . http://tahe.id.findViewById(R. // bouton Exécuter 40. 35. private int b.id. 47. Bundle savedInstanceState) { 26. 58. 52. // on retourne la vue 51. 21.id. 30. private boolean isPageValid() { 60. container. // on crée la vue du fragment à partir de sa définition XML 29. // réponses des actions et tâches 49. 48. }).id. @Override 43.edt_a).id. ligne 31 : le fragment est associé à la vue XML [vue_01] . txtErrorAleas = (TextView) view. 28. 54. 37.20.setText("").inflate(R.findViewById(R.. // l'activité private MainActivity activité. false). @Override public View onCreateView(LayoutInflater inflater.btn_Executer).developpez. edtB = (EditText) view. 25.findViewById(R. On en profite pour définir : • des références sur les différents composants de la vue. 32. 22. txtErrorAleas. txtErrorIntervalle = (TextView) view. // les messages d'erreur 36. edtNbAleas = (EditText) view.vue_01. ligne 49 : une référence sur l'objet [ListView] qui va afficher la liste d'objets renvoyée par la couche [métier] .. ViewGroup container.edt_b). ligne 27 : la méthode [onCreateView] est exécutée une fois lors de l'instanciation initiale du fragment. } 53. btnExecuter = (Button) view. activité = (MainActivity) getActivity(). } • • • • • • • • ligne 6 : la classe est de type [Fragment] . ligne 51 : on rend la vue créée . 34. View view = inflater.findViewById(R. • un gestionnaire pour le clic sur le bouton [Exécuter] . 61.txt_errorNbAleas). .edt_nbaleas). protected void doExecuter() { 2. edtA = (EditText) view. } 62. // on vérifie la validité des données saisies 59. lignes 32-39 : on récupère les références des objets de la vue . 50. 45.layout. 63. .txt_errorIntervalle). doExecuter().. 56. } 57. 23. 33..findViewById(R. 41.com 60/157 . 39.findViewById(R. public void onClick(View arg0) { 44. ligne 29 : on mémorise une référence sur l'activité . return view.

int resource. ligne 23 : le composant [listRéponses] de type [ListView] va afficher la liste de [String] que nous venons de construire.getText().setAdapter(new ArrayAdapter<String>(activité. 20.layout.com 61/157 .toString()). nbAleas = Integer. On obtient une liste d'objets où chaque objet est de type [Integer] ou [AleaException] . 6.id.getAleas(a. private boolean isPageValid() { 3. 5.simple_list_item_1. nbAleas = 0.text1. // on affiche les réponses 23.layout. strings. strings.4.toString()).setAdapter] est la suivante : public void setAdapter (ListAdapter adapter) [ListAdapter] est une interface. 18.R. 11. 10. listRéponses. 7. Cette vue peut avoir une complexité quelconque. 5. } else { 19. 24.simple_list_item_1. C'est la méthode utillisée ligne 23 pour afficher la liste [strings].getMétier(). 9.developpez. List<String> strings = new ArrayList<String>().simple_list_item_1]. // saisie du nombre de nombres aléatoires 4. on crée la liste de [String] qui va être affichée par le composant de type [ListView] de la vue . Le travail du développeur est de créer la vue [resource] qui va afficher chaque élément du [ListView].layout.text1]. android. strings)).id. 8.setAdapter(new ArrayAdapter<String>(activité. android. lignes 14-21 : à partir de la liste d'objets obtenue. C'est le développeur qui la construit en fonction de ses besoins . android. [resource] est l'entier identifiant la vue utilisée pour afficher un élément du [ListView].R.setText(""). Boolean erreur = false. 13. 7. int nbErreurs = 0.add(o. List<Object> data = activité. // on crée une liste de String à partir de ces données 14. } 22.R. } • • • • lignes 6-8 : avant d'exécuter l'action demandée. La validité des valeurs saisies est vérifiée par la méthode [isPageValid] suivante : 1. android. La classe [ArrayAdapter] est une classe implémentant cette interface. new String[]{})).R. // on vérifie la validité des données saisies 2. http://tahe. La chaîne affichée le sera par ce composant . for (Object o : data) { 16. on vérifie que les valeurs saisies sont correctes . Pour le cas simple où on ne désire afficher qu'une simple chaîne de caractères comme ici.getMessage()). La signature de [ListView. [objects] : la liste d'objets affichés par le [ListView]. } // on efface les réponses précédentes listRéponses. txtErrorIntervalle. nbAleas).parseInt(edtNbAleas. int textViewResourceId.R.id. try { 8. } 21. Le constructeur utilisé ici est le suivant : public ArrayAdapter (Context context. b.text1. List<T> objects) • • • • [context] est l'activité qui affiche le [ListView] . Android fournit la vue identifiée par [android. 6. Celle-ci contient un composant [TextView] identifié par [android. 15. // on teste la validité des saisies if (!isPageValid()) { return. // on appelle la couche métier pour obtenir les nombres aléatoires 12. if (o instanceof Exception) { 17. [textViewResourceId] est l'entier identifiant un composant [TextView] dans la vue [resource]. Une référence de celle-ci a été stockée dans l'activité. ligne 12 : la liste des nombres aléatoires est demandée à la couche [métier].R. La méthode [toString] des objets est utilisée pour afficher l'objet dans le [TextView] identifié par [textViewResourceId] dans la vue identifiée par [resource].add(((Exception) o).

} catch (Exception ex) { erreur = true. 38. 19. 8.9. } catch (Exception ex) { erreur = true. 20. txtErrorIntervalle.txt_errorIntervalle). 29.7 Exécution Créez un contexte d'exécution pour ce projet et exécutez-le. 37. 31. 36.1.com 62/157 .getText(). 12.8 Mavenisation du projet Mavenisez le projet [exemple-07] en suivant la méthode suivie pour le projet [exemple-04]. 46. 39. } catch (Exception ex) { erreur = true.toString()).parseInt(edtB.txt_errorNbAleas). 25.toString()). erreur = false. 35. 40. 24. } // erreur ? if (erreur) { nbErreurs++. 45. txtErrorIntervalle. 32. 8. 11. Ci-dessus on a vérifié simplement que les valeurs saisies étaient des nombres entiers. } // erreur ? if (erreur) { nbErreurs++.xml] les caractéristiques du projet de la façon suivante : <modelVersion>4. La couche [métier] est plus exigeante.string.string. Vous obtenez l'erreur suivante : http://tahe.setText(R. 44. 26. 21.getText(). } erreur = (nbAleas < 1). } // saisie de a a = 0. 28. 10.setText(R.parseInt(edtA. 17.developpez. 47. erreur = false. 33. 23. 18. Si vous entrez un nombre a supérieur au nombre b.txt_errorIntervalle).0. 14.1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-07</name> Lorsque votre projet ne présente plus d'erreurs de syntaxe exécutez le. 34. 15. } // retour return (nbErreurs == 0). Modifiez dans [pom. 22.setText(R. erreur = b < a. try { a = Integer. 30. 41. la couche [métier] vous renverra une exception. } // saisie de b b = 0. try { b = Integer. 27.0. txtErrorAleas. 13.0</modelVersion> <groupId>exemples</groupId> <artifactId>exemple-07</artifactId> <version>0.string. 16.1. 42. } // erreur ? if (erreur) { nbErreurs++. 43.

<dependencies> <!-.google.On trouve les logs d'Android dans la fenêtre [Logcat] d'Eclipse : Les logs indiquent que l'activité [istia. Il faut ajouter cette dépendance dans le fichier [pom.activity.MainActivity] n'a pas été trouvée dans le binaire [android-2. 2. 3. Examinons le [Build Path] [1] du projet : 2 1 • en [2].Android --> <dependency> <groupId>com.android.st. 5.apk]. la bibliothèque [android-support-v4] est utilisée dans le cadre du projet Eclipse non mavenisé.xml] : 1.com 63/157 . 4.developpez.android</groupId> <artifactId>android</artifactId> http://tahe. Le problème est complexe car les logs ne nous aident en rien.

souvent pour une même version mais sur des machines différentes.version}</version> 7.Support Android .6. <groupId>com. <artifactId>support-v4</artifactId> 4.Dex Loader] Unable to execute dex: Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServic eInfoVersionImpl. 2. </dependency> 15. <version>r6</version> 14. http://tahe. Il indique qu'il y a plusieurs binaires (dex) Android pour [android/support/v4].com 64/157 . [2013-12-08 09:57:45 . <!-.NE PAS OUBLIER!!! --> 10. </dependency> 9. </dependency> Les différences de comportement des projets Android-Maven d'Eclipse.android</groupId> 12.google. [2013-12-08 09:57:45 . <scope>provided</scope> 8. <scope>provided</scope> 6. il faut ajouter la portée [provided] à la dépendance Maven [android/support/v4] ligne 5 ci-dessous : 1. <artifactId>support-v4</artifactId> 13.android</groupId> 3. ont été un constant challenge dans l'écriture de ce document. <dependency> 2. <groupId>com. Dans ce cas. <version>r6</version> 5. Il doit maintenant fonctionner.exemple-07] Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServic eInfoVersionImpl. <version>${platform. On remarque cependant qu'avec certaines configurations d'Eclipse.developpez. Le message d'erreur est plus clair. Il y a probablement un point de configuration des projets Maven / Android qui m'a échappé. on a de nouveau une erreur : 1. Exécutez de nouveau le projet.google. <dependency> 11. </dependencies> Les lignes 10-14 ajoutent la dépendance manquante.

1.1 Le projet Eclipse Nous allons créer un projet web de type [Spring MVC]. 9.b]. PUT. Le client • sa couche [DAO] . On aura maintenant l'architecture suivante : Vue Utilisateur Activité Android Couche [metier] Couche [DAO] Serveur On a ajouté à l'application Android une couche [DAO] pour communiquer avec le serveur distant. Nous allons décrire l'application dans l'ordre suivant : Le serveur • sa couche [métier] . Ce serveur aura une architecture à deux couches suivante : Couche [web / Rest] Clients Couche [metier] Spring / Tomcat La couche [web] aura un fonctionnement de type REST (REpresentational State Transfer). celle où l'application Android communique avec des services web distants. Prenez l'option [File / New / Other] http://tahe. • son service REST implémenté avec Spring MVC . • sa couche [métier] .9 Exemple-08 : architecture client / serveur Nous abordons une architecture courante pour une application Android. Ce type de service web attend des requêtes HTTP du type : [commande] URL avec [commande] dans [GET. Cette liste sera renvoyée sous forme d'une unique chaîne JSON. Elle communiquera avec le serveur qui génère les nombres aléatoires affichés par la tablette Android. Ici notre service web traitera une unique URL de type [/random/a/b/n] qui renverra une liste de n nombres aléatoires dans l'intervalle [a.1 Spring MVC Clients Couche [web / Spring MVC] Couche [metier] Spring / Tomcat 9. il envoie une information sous forme XML ou JSON ( JavaScript Object Notation). POST] comme il est habituel. POST.developpez.com 65/157 . DELETE]. Au lieu de renvoyer une page HTML sur les commandes [GET.

On obtient alors la page suivante : http://tahe. on choisit un projet de type [Spring MVC Project] . Le projet Eclipse ainsi créé est le suivant [6] : 6 7 Le projet créé est un projet web qu'on peut immédiatement exécuter : [clic droit sur le projet / Run as / Run on server / choisir vFabric tc Server]. en [4]. on le nomme [exemple-08-server] . Ici on indique que les composants Spring seront trouvés dans le paquetage [istia. on donne le nom du paquetage de plus haut niveau de notre application.aleas] . on choisit un projet de type [Spring Project] .com 66/157 . en [3]. 5 • en [5]. on lui associe un dossier inexistant ou vide.st. en [2].3 4 1 2 • • • • en [1].developpez.

</listener> http://tahe. <context-param> 8.com/xml/ns/javaee" 3.xsd"> 5. Les autres branches sont des images de cette branche présentées sous un angle différent. 12. Nous allons nous intéresser qu'à une seule.xml]. <param-value>/WEB-INF/spring/root-context.developpez.xml</param-value> 10. L'application étant une application web. Nous verrons un peu plus loin que la raison véritable est à chercher dans le fichier [pom.sun.ContextLoaderListener</listener-class> 15. <?xml version="1.org/2001/XMLSchema-instance" 4.st. Cela est reflété dans l'arborescence ci-dessus : • les vues sont des pages JSP (Java Server Pages) placées dans le dossier [views] .com/xml/ns/javaee http://java. <!-. 6.The definition of the Root Spring Container shared by all Servlets and Filters --> 7.context. elle est contrôlée comme toute application web Java par le fichier [WEB-INF / web. la branche [src] [1]. Celui-ci est le suivant : 1. • les contrôleurs sont des classes Java placées dans le package [istia.2 Anatomie d'un projet Spring MVC Le projet [exemple-08-server] affiche beaucoup de branches.com/xml/ns/javaee/web-app_2_5.sun. <!-. <listener-class>org.com 67/157 .xml]. xsi:schemaLocation="http://java. <param-name>contextConfigLocation</param-name> 9. 3 1 2 Spring MVC est comme son nom l'indique un framework MVC (Modèle – Vue – Contrôleur).1.Creates the Spring Container shared by all Servlets and Filters --> 13.w3.0" encoding="UTF-8"?> 2.5" xmlns="http://java.8 On notera en [8] que l'assistant a utilisé comme nom de notre application le dernier terme du nom du package donné à l'étape [5]. <web-app version="2. <listener> 14. xmlns:xsi="http://www. 9.sun. </context-param> 11.web.aleas] [3] .springframework.

w3.org/schema/mvc" 3.springframework.DispatcherServlet</servlet-class> 21. <load-on-startup>1</load-on-startup> 26.springframework. </servlet-mapping> 32.org/schema/mvc http://www. 33.xml</param-value> 24. 6. ligne 19 : le nom donné à la servlet – peut être quelconque . lignes 28-31 : cette balise associe des URL à une servlet. C'est ici une classe du framework Spring MVC. <beans:beans xmlns="http://www. <servlet-name>appServlet</servlet-name> 30.web.springframework.xsd 7. C'est le contrôleur principal. le C du MVC.xml].developpez. Ce sont ces derniers qui traitent réellement les demandes. <servlet-class>org.org/schema/mvc/spring-mvc. ligne 20 : la classe de la servlet.xsd"> http://tahe.servlet.xml] a défini une unique servlet appelée [appServlet].xsd 8. <servlet-name>appServlet</servlet-name> 20.org/schema/beans" 3. <param-name>contextConfigLocation</param-name> 23. Celle-ci n'en définit qu'une aux lignes 18-26 . <init-param> 22.springframework.org/schema/beans" 5.org/schema/context/spring-context.springframework. 17. donc par la servlet définie aux lignes 18-26 . http://www.org/schema/beans/spring-beans. <!-. http://www. <beans xmlns="http://www.org/schema/beans/spring-beans. Celui-ci est ici le suivant : 1. C'est le contrôleur principal. lignes 22-23 : indiquent à la servlet [DispatcherServlet] où elle va trouver son fichier de configuration .org/schema/context" 6.xsd"> 5.springframework. Une aplication peut définir plusieurs servlets.org/schema/beans http://www.w3. <param-value>/WEB-INF/spring/appServlet/servlet-context.springframework.org/2001/XMLSchema-instance" 4. La ligne 30 indique que toute URL doit être traitée par la servlet indiquée ligne 29. C'est ici qu'on les met .Processes application requests --> 18.springframework. xmlns:xsi="http://www. </init-param> 25.springframework. <servlet> 19.springframework. <?xml version="1. xsi:schemaLocation="http://www. Par défaut.springframework. </web-app> • • • • • • • • lignes 18-26 : définissent la classe qui va traiter toutes les demandes faites à l'application.16. lignes 21-24 : une servlet peut accepter des paramètres de configuration. Nous en avons vu un précédemment : la classe [HomeController] . les classes qui traitent les demandes sont appelées des [servlets]. elle est configurée également par le fichier [appServlet / servlet-context. Le fichier [web. Ce contrôleur principal route les demandes vers des contrôleurs secondaires appelés souvent simplement contrôleurs. <?xml version="1. Celui-ci est le suivant : 1. En l'absence de cette balise. xmlns:context="http://www. xmlns:beans="http://www.springframework. 28. xmlns:xsi="http://www.xml]. la servlet n'est chargée que lorsque la première demande pour elle arrive . ligne 25 : indique que la servlet doit être chargée dès que le serveur démarre.org/schema/context http://www.org/schema/beans http://www. La ligne 23 ci-dessus indique que le contrôleur principal est configuré par le fichier [/WEB-INF/spring/root-context. xsi:schemaLocation="http://www.springframework.0" encoding="UTF-8"?> 2. <!-.Root Context: defines shared resources visible to all other web components --> 7. <url-pattern>/</url-pattern> 31. </servlet> 27. <servlet-mapping> 29. le C du MVC .org/2001/XMLSchema-instance" 4. 8.com 68/157 .0" encoding="UTF-8"?> 2. </beans> Il est vide.

• @Service : qui désigne un composant Spring à instancier une fois (singleton). formattedDate ). } http://tahe.developpez.addAttribute("serverTime".Enables the Spring MVC @Controller programming model --> <annotation-driven /> <!-. 11. // locale : locale de l'application .getDateTimeInstance(DateFormat.view.format(date).web.9.GET) 11. import java. . Date date = new Date().jsp] de s'afficher 22. 23.Resolves views selected for rendering by @Controllers to . L'unique contrôleur est la classe [HomeController] suivante : 1. <!-. 24. // on demande à la vue [home.jsp resources in the /WEBINF/views directory --> 19.springframework. 15. locale). return "home".st. 6. On pourra mettre des images.aleas. Ainsi les lignes 15-22 nous seront inutiles. Nous en aurons de deux types : • @Controller : qui désigne une classe Java capable de traiter certaines demandes des clients. <!-.com 69/157 . @Controller 7.aleas] pour y trouver les composants Spring de l'application.st. <beans:bean class="org. 19. method = RequestMethod. 2.. 25.DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-.jsp] .st. // la méthode [home] traite la demande GET / 10. 10. 17. scripts Javascript. 15. package istia. @RequestMapping(value = "/". Nous serons amenés à compléter ce fichier de configuration.InternalResourceViewResolver"> 20.servlet. Model model) { 12. 3. 14.Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory --> 16. model.aleas" /> 25. </beans:bean> 23.injectée automatiquement par Spring 13. 9. <beans:property name="suffix" value=". <context:component-scan base-package="istia. </beans:beans> • • • • la ligne 13 indique que l'application est configurée par des annotations placées dans le code Java des classes . Nous l'utiliserons pour l'unique cotrôleur de cette application . feuilles de styles dans ce dossier . // cette date est mise dans le modèle associée à la clé [serverTime] 20. lignes 19-22 : indique qu'une vue nommée [V] sera implémentée par le fichier [WEB-INF / views / V. // model : le modèle pour la vue qui sera retournée par la méthode 14. 13. <beans:property name="prefix" value="/WEB-INF/views/" /> 21. // la date du jour 16.text. <resources mapping="/resources/**" location="/resources/" /> 17. DateFormat dateFormat = DateFormat.LONG. 21. String formattedDate = dateFormat. 12. 18. 5. public String home(Locale locale. 18. DateFormat. Il a été généré par défaut pour des contrôleurs qui affichent des pages HTML alors que nous voulons rendre des chaînes JSON.jsp" /> 22. ligne 24 : on demande à spring de scanner le dossier [istia.LONG. } 24. Nous l'utiliserons pour l'implémentation de la couche [métier] . 4.DateFormat.. la ligne 16 indique que l'URL [/resources/**] sera associée au dossier [resources] de l'application. public class HomeController { 8.





ligne 6 : l'annotation [@Controller] fait de la classe [HomeController] une classe capable de traiter les requêtes des clients
de l'application web. La classe est un contrôleur C, le C du MVC ;
la ligne 10 indique quelle requête la méthode [home] peut traiter. Elle traite la requête [GET /] ;
ligne 11 : la méthode reçoit deux paramètres :

le premier est de type [Locale]. Spring reconnaît ce type et injecte automatiquement la locale du serveur,
ici [fr_FR] pour indiquer le français (fr) de France (FR) ;

le second est de type [Model]. Spring reconnaît ce type et injecte automatiquement une instance
[Model] vide. Elle formera le modèle M de la vue V affichée par la méthode, le M et le V du MVC ;
lignes 16-18 : une date / heure est calculée ;
ligne 20 : le type [Model] s'utilise comme un dictionnaire. On y met des valeurs associées à des clés ;
ligne 22 : la méthode doit rendre le nom de la vue qui doit s'afficher, ici celle qui s'appelle [home]. Grâce au fichier de
configuration présenté, c'est la page [WEB-INF / views / home.jsp] qui va être affichée. Elle utilisera comme modèle
l'objet [Model] utilisé par la méthode ;

La page JSP [home.jsp] est la suivante :
1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
2. <%@ page session="false" %>
3. <html>
4. <head>
5.
<title>Home</title>
6. </head>
7. <body>
8. <h1>
9.
Hello world!
10. </h1>
11.
12. <P> The time on the server is ${serverTime}. </P>
13. </body>
14. </html>

la ligne 1 est souvent nécessaire mais pas ici. Elle définit ce qu'on appelle une bibliothèque de balises qu'on peut alors
utiliser dans la page JSP ;
la ligne 12 utilise la variable [${serverTime}]. Sa valeur est cherchée dans le modèle de la page. Celui-ci est le modèle
construit par la méthode [home] du contrôleur [HomeController]. Cette méthode avait créé une valeur associée à la clé
[serverTime]. C'est cette valeur qui est ici récupérée.

Voilà ! On a fait le tour des éléments d'une application Spring MVC. Celle générée n'est pas tout à fait adaptée pour faire un serveur
REST. Nous serons amenés à modifier sa configuration.
L'application générée est une application Maven dont le fichier [pom.xml] est le suivant :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/mavenv4_0_0.xsd">
4.
<modelVersion>4.0.0</modelVersion>
5.
<groupId>istia.st</groupId>
6.
<artifactId>aleas</artifactId>
7.
<name>exemple-08-server</name>
8.
<packaging>war</packaging>
9.
<version>1.0.0-BUILD-SNAPSHOT</version>
10.
<properties>
11.
<java-version>1.6</java-version>
12.
<org.springframework-version>3.1.1.RELEASE</org.springframework-version>
13.
<org.aspectj-version>1.6.10</org.aspectj-version>
14.
<org.slf4j-version>1.6.6</org.slf4j-version>
15.
</properties>
16.
<dependencies>
17.
<!-- Spring -->
18.
<dependency>

http://tahe.developpez.com

70/157

19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.

<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>

http://tahe.developpez.com

71/157

82.
</exclusions>
83.
<scope>runtime</scope>
84.
</dependency>
85.
86.
<!-- @Inject -->
87.
<dependency>
88.
<groupId>javax.inject</groupId>
89.
<artifactId>javax.inject</artifactId>
90.
<version>1</version>
91.
</dependency>
92.
93.
<!-- Servlet -->
94.
<dependency>
95.
<groupId>javax.servlet</groupId>
96.
<artifactId>servlet-api</artifactId>
97.
<version>2.5</version>
98.
<scope>provided</scope>
99.
</dependency>
100.
<dependency>
101.
<groupId>javax.servlet.jsp</groupId>
102.
<artifactId>jsp-api</artifactId>
103.
<version>2.1</version>
104.
<scope>provided</scope>
105.
</dependency>
106.
<dependency>
107.
<groupId>javax.servlet</groupId>
108.
<artifactId>jstl</artifactId>
109.
<version>1.2</version>
110.
</dependency>
111.
112.
<!-- Test -->
113.
<dependency>
114.
<groupId>junit</groupId>
115.
<artifactId>junit</artifactId>
116.
<version>4.7</version>
117.
<scope>test</scope>
118.
</dependency>
119.
</dependencies>
120.
<build>
121.
<plugins>
122.
<plugin>
123.
<artifactId>maven-eclipse-plugin</artifactId>
124.
<version>2.9</version>
125.
<configuration>
126.
<additionalProjectnatures>
127.
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
128.
</additionalProjectnatures>
129.
<additionalBuildcommands>
130.
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
131.
</additionalBuildcommands>
132.
<downloadSources>true</downloadSources>
133.
<downloadJavadocs>true</downloadJavadocs>
134.
</configuration>
135.
</plugin>
136.
<plugin>
137.
<groupId>org.apache.maven.plugins</groupId>
138.
<artifactId>maven-compiler-plugin</artifactId>
139.
<version>2.5.1</version>
140.
<configuration>
141.
<source>1.6</source>
142.
<target>1.6</target>

http://tahe.developpez.com

72/157

Main</mainClass> </configuration> </plugin> </plugins> </build> </project> La plupart des dépendances sont ici inutiles.test.com 73/157 . réexécutez le projet. 151. C'est donc l'[artifactId] du projet Maven qui est utilisé pour donner son nom à l'application web.codehaus. Vous pouvez alors procéder ainsi : • supprimez le projet [clic droit sur projet / Delete (sans suppression de dossier)] .developpez. 145. 9.int1.1</version> <configuration> <mainClass>org. • réexécutez le projet . 153. 154. La page web affichée est désormais la suivante : 1 En [1]. 152. • vérifiez l'encodage UTF-8 du projet . 155. l'URL a changé.2 Le serveur REST Rappelons que nous voulons construire l'architecture suivante : Clients Couche [web / Rest] Couche [metier] Spring / Tomcat Nous créons un nouveau projet [exemple-08-server-rest] par duplication du projet [exemple-08-server].mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1. 147. 148. 146. L'application générée a été configurée pour une application Spring MVC complète. 157. <compilerArgument>-Xlint:all</compilerArgument> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> </configuration> </plugin> <plugin> <groupId>org. 150. Modifiez les lignes 5 et 6 ci-dessus de la façon suivante : <groupId>exemples</groupId> <artifactId>exemple-08-server</artifactId> Ceci fait. Parfois l'URL ne change pas. 158. 149.2. http://tahe. 156. • réimportez-le . 144.143.

rest] pour y loger la couche [web] .1 en [1].developpez.0.aleas. nous créons deux nouveaux packages [istia.com 74/157 .2.xml] de la façon suivante : <groupId>exemples</groupId> <artifactId>exemple-08-server-rest</artifactId> <name>exemple-08-server-rest</name> <packaging>war</packaging> <version>1.aleas.st.0-BUILD-SNAPSHOT</version> Nous modifions le nouveau projet de la façon suivante : 1 • 9.Modifiez les caractéristiques du projet dans [pom. La couche [métier] Couche [web / Rest] Clients Couche [metier] Spring / Tomcat La couche [métier] aura l'interface [IMetier] suivante : http://tahe.metier] pour y loger la couche [métier] et [istia.st.

if (réponses. réponses. if (n < 1) { 18.aleas. package istia. 40. if (a >= b) { 27. 28. List<Object> réponses = new ArrayList<Object>(). 3.Service. réponses. if (a < 0) { 21. réponses. // qqs vérifications 17.List.b]. 25.b] doit être supérieur à 0")). 43. int nombre = random. // la liste des objets 15. 6. 8. 2. for (int i = 0.nextInt(3).ArrayList. 6. } 26. } http://tahe.util.nextInt(b . 9. package istia.springframework. 4. } else { 41. public List<Object> getAleas(int a. // on génère les nombres aléatoires 34. } 33. 4.st. public class Metier implements IMetier { 11.b] doit être supérieur à 0")). i < n. Random random = new Random(). return réponses.add(new AleaException("Le nombre a de l'intervalle [a. if (b < 0) { 24.add(Integer. 32. 3.metier. 5. return réponses.add(new AleaException("Le nombre d'entier aléatoires demandé doit être supérieur ou égal à 1")). 8. 19. } 20. @Override 13. import org. } 45. int b.com 75/157 . • ligne 7 : la méthode qui génère n nombres aléatoires entre [a. } 29. public interface IMetier { public List<Object> getAleas(int a. 7. // résultat 46.st. } 44.metier.add(new AleaException("Exception aléatoire")).util.valueOf(a + random.add(new AleaException("Le nombre b de l'intervalle [a.util. int n) { 14. int b.add(new AleaException("Dans l'intervalle [a. int n). 35. 47. réponses.size() != 0) { 31. on doit avoir a< b")).developpez. i++) { 36. 7. 38.stereotype.b] 42. } Le code de la classe [Metier] implémentant cette interface est le suivant : 1. if (nombre == 0) { 39. 16. import java. // erreur ? 30.aleas. import java.a + 1))). // on génère une exception aléatoire 1 fois / 3 37.1. @Service 10. import java. 22. réponses. 12.Random. réponses. } 23. 2.List. // sinon on rend un nombre aléatoire entre deux bornes [a. 5.util.b] import java.

public class AleaException extends RuntimeException { 4. } 9. notamment le contrôleur [home]. public AleaException() { 8. public AleaException(String detailMessage.aleas. } 9. les objets que le client doit envoyer et celui qu'il reçoit. 20. 2. Un service REST (RepresEntational State Transfer) est un service HTTP répondant aux demandes GET. 22.. public AleaException(String detailMessage) { 11. } 13. 10.server. 6. Throwable throwable) { 19.developpez. throwable).AleaException. } 17. La classe [Metier] crée des exceptions de type [AleaException] : 1. } 21. super(throwable). 16. super(detailMessage).rest. 14. 3. DELETE d'un client HTTP. 4. Nous appelons REST notre service parce qu'il est implémenté par un service de Spring qu'on a l'habitude d'appeler service REST et non parce qu'il respecte la définition formelle des services REST. } Nous ne commentons pas la classe : nous l'avons déjà rencontrée dans [exemple-07]. Nous ajoutons le contrôleur [AleaController] suivant : 1. super(detailMessage.metier. 3. On notera simplement ligne 9 l'annotation Spring [@Service] qui va faire que Spring va instancier la classe en un unique exemplaire et rendre sa référence disponible pour d'autres composants Spring.st. 12. POST. package istia. public AleaException(Throwable throwable) { 15. import istia. 5. PUT. private static final long serialVersionUID = 1L. package istia..2. 2. 7. Sa définition formelle indique pour chacune de ces méthodes. .2 Le service REST Clients Couche [web / Rest] Couche [metier] Spring / Tomcat Le service REST est implémenté par Spring MVC.aleas. http://tahe.st.48.st. 18.com 76/157 .metier.

writeValueAsString(aleas). 25.add(i. aleas = metier.5. La conversion de String vers le type des paramètres peut échouer. for (int i = 0. 22. 30. on utilise la bibliothèque JSON [Jackson] : • new ObjectMapper() : rend un objet capable de sérialiser un objet en JSON et de désérialiser une chaîne JSON en objet. Nous verrons comment.size(). } • • • ligne 16 : la méthode qui génère les nombres aléatoires. Lorsqu'elle s'exécute. produces = "application/json. 21.developpez. Cela se fait via l'annotation @PathVariable(" x "). } 37. 32. } 34. 38. 36. On notera que {a}. // on gère les données reçues 23. l'entête HTTP Content-Type:application/json. c'est parce que le serveur web a reçu une requête HTTP GET pour l'URL de la ligne 14 . • [new ObjectMapper(). String msg = ((AleaException) o). Par ailleurs.getAleas(a. method = RequestMethod. @Controller 7. Résumons : si avec un navigateur je demande l'URL / 100/200/5. JsonMappingException. // on ne retient de l'exception que son message 27. // on enlève l'élément n° i actuel 29. Pour cela.get(i). // on rend la liste sérialisée en Json 35. @PathVariable("n") int n) throws JsonGenerationException. aleas. IOException { 18. @ResponseBody 16.GET. si elle s'exécute. On ne passe pas par l'intermédiaire d'une vue .getAleas rend une liste d'objets où l'objet est de type [AleaException] ou [Integer] . Spring MVC lance alors une exception. i < aleas. {b} et {n} sont des composantes d'une URL et sont donc de type String. ligne 14 : l'URL traitée est de la forme /{a}/{b}/{n} où {x} représente une variable. 19. 12.com 77/157 . b. private IMetier metier. 17. Cela fixe la valeur de l'entête HTTP [Content-type]. msg). ligne 16 : la réponse est de type [String] .remove(i). 9. ligne 15 : l'annotation [@ResponseBody] indique que la méthode va générer la réponse au client. } 33. 6. b=200 et n=5 . {b} et {n} sont affectées aux paramètres de la méthode ligne 16. // couche métier 10.WriteValueAsString(Object o)] : crée la chaîne JSON de l'objet o. 28. ligne 14 : le type de la réponse est précisée par l'attribut [produces]. @RequestMapping(value = "/{a}/{b}/{n}". Object o = aleas. aleas. public class AleaController { 8.b]. ligne 35 : on veut renvoyer la chaîne JSON de la liste [aleas]. // nombre aléatoire 14. List<Object> aleas = null. // on le remplace par la nouvelle valeur 31. la chaîne JSON correspondant à l'objet [aleas] aura la forme d'une liste contenant des nombres et des messages d'erreur : http://tahe. public String getAleas(@PathVariable("a") int a. Son nom n'a pas d'importance.charset=UTF-8 • • • • • • va être envoyé au client. lignes 23-33 : une boucle pour remplacer dans la liste reçue chaque exception par le message qu'elle contient . Cette méthode peut lancer les exceptions indiquées lignes 16 et 17 . Les variables {a}. les champs de la ligne 11 ont été initialisés par Spring MVC. if (o instanceof AleaException) { 26. la méthode getAleas de la ligne 16 s'exécutera avec les paramètres entiers a=100. n). Le client peut ou non utiliser cette information ligne 21 : on demande à la couche [métier] n nombres aléatoires dans l'intervalle [a. 13. Ici. On se souvient que la méthode [metier]. return new ObjectMapper(). // on utilise la couche métier 20. @PathVariable("b") int b. i++) { 24.charset=UTF-8" ) 15. @Inject 11.getMessage().

<context:component-scan base-package="istia.aleas] . • ligne 6 : il est possible de configurer Spring à l'aide d'annotations dans le code Java. Le fichier de configuration [servlet-context] reste ce qu'il était : Son code est désormais le suivant : 1.171.st.."Exception aléatoire".0" encoding="UTF-8"?> <beans:beans . 6. method = RequestMethod. // nombre aléatoire 9. c-à-d une classe capable de traiter des requêtes HTTP. @PathVariable("n") int n) throws JsonGenerationException. IOException { Une autre annotation que Spring trouvera est l'annotation [@Service] de la classe [Metier] : @Service public class Metier implements IMetier { • • ligne 1 : l'annotation @Controller fait de la classe [AleaController] un contrôleur Spring MVC. 8.aleas" /> </beans:beans> Jusqu'à la ligne 6 incluse.@Inject --> <dependency> http://tahe. public String getAleas(@PathVariable("a") int a. Il y trouvera diverses annotations. 12. par exemple dans la classe [AleaController] : 1. Celles-ci sont définies par l'annotation @RequestMapping qu'on voit en ligne 9 . JsonMappingException. @ResponseBody 11. 5.["Exception aléatoire".. @RequestMapping(value = "/{a}/{b}/{n}". @Inject 6. 3."> .xml] : <!-. 7.developpez.167. @Controller 2.. // couche métier 5. private IMetier metier. public class AleaController { 3. L'annotation [@Inject] nécessite une nouvelle dépendance Maven dans [pom. 2. La ligne 6 dit à Spring d'exploiter les annotations qu'il trouvera dans le package [istia.145] C'est donc cette chaîne que recevra le client. @PathVariable("b") int b.. <?xml version="1. 4.com 78/157 . on a le contenu précédent. lignes 5 : l'annotation @Inject injecte des références sur des beans définis dans le fichier de configuration de Spring MVC ou bien par des annotations Java . 4. 8. 7.st.GET) 10.

<groupId>org. <groupId>exemples</groupId> 6.0.inject</artifactId> 39.xsd"> 4. <groupId>org. <artifactId>javax.0. <modelVersion>4. <version>${org. <version>${jackson.. </properties> 16. <version>1</version> 40. <!-.developpez.inject</groupId> 38. <dependency> 25.6</java-version> 13.org/POM/4.1.0-BUILD-SNAPSHOT</version> 10.xml] qui configure l'application est le suivant : 1.codehaus.Jackson --> 30. Comme elle implémente l'interface [IMetier] sa référence sera injectée dans le champ [metier] ligne 5 .mapper. <packaging>war</packaging> 9.version> 15. <dependency> 20. .jackson</groupId> 32.inject</artifactId> <version>1</version> </dependency> • ligne 5 : Spring recherche un composant Spring implémentant l'interface [IMetier].inject</groupId> <artifactId>javax.mapper. <build> 44.org/mavenv4_0_0.0. </dependency> 24. 17.0. </dependencies> 43. <java-version>1. <version>${org. <?xml version="1. <properties> 12.springframework-version}</version> 28.com 79/157 .springframework-version>3.org/POM/4.1.RELEASE</org.springframework-version> 14. </build> 46. Le fichier [pom.version}</version> 34. <artifactId>spring-context</artifactId> 22.6</jackson.Spring --> 19. Un composant Spring est un composant défini dans un fichier de configuration de Spring ou bien par des annotations Java. <groupId>org.@Inject --> 36. <!-. lignes 30-34 : la dépendance sur la librairie JSON Jackson . </dependency> 35.0" xmlns:xsi="http://www. <name>exemple-08-server-rest</name> 8. <version>1.<groupId>javax. <jackson. A cause de son annotation [@Service].version>1.springframework-version}</version> 23. </dependency> 41. <artifactId>spring-webmvc</artifactId> 27.0 http://maven. <dependency> 31.apache. <artifactId>jackson-mapper-asl</artifactId> 33. <dependency> 37.mapper. 45.0" encoding="UTF-8"?> 2. la classe [Metier] fait partie des composants gérés par Spring..springframework</groupId> 26. <groupId>javax. 11. 42. <!-. </dependency> 29. <artifactId>exemple-08-server-rest</artifactId> 7. <project xmlns="http://maven. http://tahe. <org. xsi:schemaLocation="http://maven.apache.w3. </project> • • lignes 19-28 : les dépendances sur Spring .5.org/2001/XMLSchema-instance" 3.springframework</groupId> 21. <dependencies> 18.0</modelVersion> 5.apache.

2.• 9. on exécute le projet [exemple-08-server-rest]. Voici une copie d'écran qu'on peut obtenir : Selon le navigateur que vous utilisez.developpez.3 lignes 36-40 : la dépendance de l'annotation [@Inject] Exécution du serveur REST Ci-dessus.com 80/157 . vous pouvez avoir une erreur analogue à la suivante : Cela signifie que le navigateur ne reconnaît pas l'entête HTTP envoyé par le serveur : Content-Type:application/json.charset=UTF-8 http://tahe.

Le navigateur intégré d'Eclipse peut dans certaines configurations ne pas reconnaître l'entête [application/json]. 9. method = RequestMethod. la couche [DAO] qui s'adresse au service REST que nous avons étudié précédemment.xml] de la façon suivante : <groupId>exemples</groupId> http://tahe. Un client recevant un entête HTTP [application/json] peut essayer de sérialiser la chaîne JSON en un objet alors que s'il reçoit l'entête [text/plain] il n'essaiera pas d'interpréter la chaîne reçue. 3.com 81/157 . Cela fait une différence.charset=UTF-8") pour indiquer que nous envoyons du texte.GET. 9.3 Le client Android du serveur REST Le client Android aura l'architecture suivante : Vue Utilisateur Couche [métier] Couche [DAO] Serveur Activité Android Le client a trois composantes : 1. produces = "text/plain. Cela ne gênera pas le client que nous allons écrire.3. Modifiez les caractéristiques du nouveau projet dans [pom.developpez. Le projet Android Le projet Android s'appellera [exemple-08-client-rest] et est obtenu par recopie du projet [exemple-07].charset=UTF-8") C'est l'attribut [produces] qui a généré l'entête HTTP. la couche [métier] qui va présenter une interface analogue à celle de la couche [métier] du serveur . Nous pouvons également écrire : @RequestMapping(value = "/{a}/{b}/{n}". 2. method = RequestMethod. produces = "application/json.GET. Essayez alors un autre navigateur ou mettez [text/plain] dans l'attribut [produces].Cette ligne vient du contrôleur [AleaController] : @RequestMapping(value = "/{a}/{b}/{n}". En effet. on veut réutiliser la couche [Android] de ce projet.1 la couche [Android] que nous avons étudiée dans l'exemple [exemple-07] .

android.st.android" 4. <?xml version="1. 25. Si on l'oublie.st.android. <intent-filter> 23.2 Le manifeste de l'application Android Le fichier [AndroidManifest. android:label="@string/app_name" 17. android:theme="@style/AppTheme" > 18.0" encoding="utf-8"?> 2. </intent-filter> 27.intent. android:versionName="1.LAUNCHER" /> 26.category.permission. android:windowSoftInputMode="stateHidden" > 22.xml] du projet est un peu différent de celui du projet précédent : 1.<artifactId>exemple-08-client-rest</artifactId> <version>0. <category android:name="android. 30. <application 14. <uses-sdk 8. <uses-permission android:name="android. android:label="@string/app_name" 21. android:targetSdkVersion="16" /> 10.0" > 6.developpez.0. android:name="istia. <action android:name="android.com/apk/res/android" 3. </application> 29.MainActivity" 20.com 82/157 .activity. 11. <activity 19.1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-08-client-rest</name> 9. android:minSdkVersion="11" 9. http://tahe. 7.MAIN" /> 24. C'est elle qui permet au client Android d'ouvrir des connexions réseau. package="istia. <manifest xmlns:android="http://schemas.INTERNET" /> 12. 13.3.action. android:icon="@drawable/ic_launcher" 16. ça ne marche pas et les messages d'erreur ne sont pas toujours explicites quant à la cause de l'erreur. android:allowBackup="true" 15. </manifest> La ligne importante est la ligne 11. </activity> 28. android:versionCode="1" 5.intent.

st. 5. 13. package istia.aleas.util. 11.st. package istia. 2. public interface IMetier { } public List<Object> getAleas(int a.. 3. 2. public void setUrlServiceRest(String url). ligne 11 : la couche [métier] a besoin de la couche [DAO]. public void setDao(IDao dao). int n). 4.st.9. import java. int b. public List<Object> getAleas(int a. import istia.metier.util.android. 6.com 83/157 .dao. 6. 7. 4.IDao. 4. C'est cette dernière qui assure les échanges avec le service REST . 10. int n).st. L'implémentation [Metier] du client est la suivante : 1.android. 12. package istia. 3. import java.developpez. Nous verrons pourquoi. 14.List. 6. 3.. 8. 7. public class Metier implements IMetier { http://tahe.android.List. } • • • ligne 9 : la méthode de génération des nombres aléatoires . 9. int b.IDao. .android. Celle du client sera analogue : 1. 5.3.metier. 5.st. ligne 13 : la couche [métier] a besoin de connaître l'adresse du service REST.dao. public interface IMetier { 8.metier.3 La couche [metier] Utilisateur Vue Activité Android Couche [metier] Couche [DAO] Serveur Rappelons l'interface de la couche [métier] du serveur : 1. import istia. 2.

54.valueOf(b)). 34. 53. 24. int b. th = th. for (String string : strings) { 55. // service REST private String urlServiceRest. // setters public void setDao(IDao dao) { this. 29. 8. } 22. 42. 37. 10. public void setUrlServiceRest(String url) { 65. 26.valueOf(a)). 64. 33. paramètres. String. objets. 66. String>().executeRestService("get". null.getType()). 14. 20. 38. 15. 51.fromJson(réponse. } catch (NumberFormatException ex) { 58. List<Object> objets = new ArrayList<Object>(). 19. 46. } 60. urlService. }. } public List<Object> getAleas(int a.developpez. 30. // pas d'exception . 11. String.format("http://%s/{a}/{b}/{n}". 59. 57. 23. 13. 47. 21. try { 56.add(string).on exploite la réponse JSON 50. 12. // couche [dao] private IDao dao. while (th != null) { messages. // paramètres service REST Map<String. 17. 43. 49. 36.com 84/157 .valueOf(string)). } 61. 41. 68. 31. // exécution service [DAO] Exception exception = null. urlServiceRest).put("b". urlServiceRest = url. 62.valueOf(n)).on récupère un [String] réponse = dao. 25.add(Integer.put("a". } http://tahe.getMessage()).add(th. 16. try { // exécution service . paramètres). } 63.getCause(). 9.dao = dao. } 67.put("n". new TypeToken<List<String>>() { 52. } return messages.on note l'exception exception = ex. 28. int n) { // adresse du service REST String urlService = String.7. String> paramètres = new HashMap<String. paramètres. 39. 18. String. 35. objets. paramètres. List<String> strings = new Gson(). Throwable th = exception. 44. 32. return objets. String réponse = null. 40. } catch (Exception ex) { // cas d'erreur . 45. 48. } // si exception if (exception != null) { List<Object> messages = new ArrayList<Object>(). 27.

On notera bien la syntaxe des variables a. ligne 19 : la méthode de génération des nombres aléatoires .fromJson(String json.com Vue Activité Android Couche [metier] Couche [DAO] Serveur 85/157 . ligne 49 : new Gson() crée l'objet Gson qui nous permet de sérialiser un objet [Gson].code. Cela nous amènera à ajouter la dépendance suivante dans le fichier [pom. La couche [DAO] Utilisateur http://tahe.gson</groupId> <artifactId>gson</artifactId> <version>${com. ligne 60 : cette liste est rendue au module appelant .executeRestService de la couche [DAO] avec les paramètres suivants : 1. 2.xml] : <!-. {b} et {n} .code. On la parcourt pour créer une liste d'objets de type [Integer] ou [String] .• • • • • • • ligne 9 : une référence sur la couche [DAO]. Class typeObjet) .google.developpez. les valeurs des variables de l'URL. la méthode " get " ou " post " de la requête HTTP à émettre.4 La bibliothèque JSON n'est pas indispensable ici pour récupérer les chaînes de caractères. ligne 52 : on instancie la liste d'objets qu'on doit rendre au module appelant . nous allons créer à partir de cette chaîne un objet List<String>.google. sous la forme d'un dictionnaire. Nous utilisons ici une bibliothèque JSON de Google appelée Gson.174. un dictionnaire des données transmises par une opération HTTP POST. lignes 23-26 : un dictionnaire dont les clés sont les variables de l'URL demandée . ligne 32 : on exécute la méthode [dao]. ligne 21 : on construit l'URL complète du service REST demandé. Donc null ici. Nous l'utilisons car elle s'avère souvent nécessaire pour traiter les réponses JSON des serveurs REST . C'est également la bibliothèque qu'utilise le framework [spring-android-resttemplate] que nous allons utiliser pour communiquer avec le serveur REST.Gson JSON Processor --> <dependency> <groupId>com. lignes 49-51 : on a reçu une chaîne du serveur de la forme suivante : [179. 3. ici les variables {a}. nous pourrons voir les messages d'erreur des exceptions qui se sont produites . puisqu'on fait une opération HTTP GET."Exception aléatoire"] Avec une bibliothèque JSON. l'URL complète du service REST à exécuter.gson-version}</version> </dependency> • • • • 9."Exception aléatoire".toJson(Object o)] et de désérialiser une chaîne JSON dans un objet [Gson].3. b et n dans l'URL . Elle sera initialisée lorsque l'activité Android instanciera la couche [métier] . on met les messages d'erreur de cette exception et de ses causes dans une liste et on rend celle-ci à l'appelant qui pour l'instant sera la vue [Vue_01].130. lignes 53-59 : la liste de [String] est un ensemble de nombres entiers aléatoires et de messages d'erreur. lignes 39-47 : si une exception se produit lors de l'appel du service REST. 4. Celle-ci affichant ce qu'elle reçoit dans un [ListView].

import java. return restTemplate. . restTemplate = new RestTemplate(). 4.getForObject(urlService.executeRestService] L'argument [method] doit avoir la valeur post ou get"). public Dao() { 15.android. Object request. 7. 9. 4. // on le configure . } 21. 20. 28. checkResponsiveness(urlService).util. } else { 38. return restTemplate. 13. 3. public void setTimeout(int millis).class. // vérification méthode HTTP 29. throw new AleaException("[dao. if (method.Map. String> paramètres) { 24.dao. method = method.android.postForObject(urlService. 32. private RestTemplate restTemplate. Object request. 8.equals("get")) { 36.. 8. 25. String urlService. throw new AleaException("[dao. // exécution service 35.. http://tahe. paramètres).toLowerCase(new Locale("fr-FR")). 22. String> paramètres). package istia. // constructeur 14. restTemplate. } catch (Exception ex) { 41. Ce temp est fixé en millisecondes.st.equals("get") && !method. private int timeout. String urlService. // on crée un objet [RestTemplate] 16. 3. la couche [DAO] lance une exception [AleaException]. if (!method.st. 5. String. request. 10.equals("post")) { 31. public interface IDao { 6. // recevoir 19. Map<String. 2.st. Map<String.activity. public class Dao implements IDao { 7. public String executeRestService(String method. Passé ce délai.AleaException. 17. 37. package istia.il doit être capable de gérer la chaîne qu'il va 18.com 86/157 .getMessageConverters().L'interface [IDao] de la couche [DAO] est la suivante : 1. } 33. } 40. paramètres). String. } • • ligne 6 : la méthode [executeRestService] dont nous avons parlé précédemment . 39. L'implémentation est la suivante : 1. 2.developpez.dao. // délai d'attente maximal 11. public String executeRestService(String method.class. // client REST 9.add(new StringHttpMessageConverter()). 12.executeRestService] Une erreur s'est produite". ex). 5. import istia. // exécution de l'appel au service REST 23. 30. ligne 8 : une méthode pour fixer un temps de réponse maximal de la part du serveur REST. // on vérifie que le serveur distant répond assez vite 26. try { 34. 6.android. // une exception est lancée sinon 27.

Le coeur du framework Spring n'a pas encore été porté sur Android. b et n . Pour avoir cette bibliothèque. // délai d'attente maximal 51. 55. On lui donne une liste de " convertisseurs de messages ". nous ajouterons une dépendance dans le fichier [pom. l'URL complète du service REST à exécuter. . } • ligne 9 : la couche [DAO] s'appuie sur un client REST fourni par la bibliothèque [spring-android-framework]. 53.replace("{". 5.42. La méthode [checkResponsiveness] est la suivante : 1. Le principal élément de cette bibliothèque est la classe [RestTemplate]. Ce convertisseur va alors transformer la chaîne JSON en l'objet qu'on lui indiquera.postForObject sont les mêmes si ce n'est le second paramètre [request] qui est la valeur postée . la méthode [checkResponsiveness] lance une exception .android</groupId> <artifactId>spring-android-rest-template</artifactId> <version>${spring-android-version}</version> </dependency> • • • • • • • • ligne 16 : l'objet [RestTemplate] est instancié .getForObject sont les suivants : • 0 : l'URL requêtée par exemple [http://localhost:8080/exemple-08-serve-rest/{a}/{b}/{n}]. Les tests montrent qu'on perd alors les caractères accentués présents dans la chaîne JSON envoyée par le serveur. ligne 38 : les paramètres de la méthode [RestTemplate]. ""). 7. } 49. } 44. ici un type [String]. On peut être tenté d'utiliser le convertisseur [GsonHttpMessageConverter]. ici les valeurs de a. la méthode " get " ou " post " de la requête HTTP à émettre. public void setTimeout(int millis) { 52. ç-à-d des classes capables de traiter la réponse du serveur REST. le champ de la ligne 9 aurait été initialisé par Spring. private void checkResponsiveness(String urlService) { 2. lignes 30-32 : vérification de la méthode HTTP . 2. sous la forme d'un dictionnaire. private void checkResponsiveness(String urlService) { 46. • 2 : un dictionnaire contenant les paramètres effectifs de l'URL. } 54. URI service = null.developpez. 48. Le " convertisseurs de messages " qui convient alors est la classe [StringHttpMessageConverter].. • 1 : la classe de la réponse. service = new URI(url).Spring Android --> <dependency> <groupId>org. Celles utilisées ici vont rendre comme résultat. par exemple dans notre cas une liste de [String] ou encore une liste d'objets [Object]. } 43. 3. 4.xml] : <!-. Alors qu'habituellement. Cette classe a de nombreuses méthodes pour dialoguer avec un service REST. ici il le sera par le constructeur. Nous recevrons donc un type [String] qu'on désérialisera ultérieurement en [List<String>] . ligne 19 : il est configuré.com 87/157 . 50. ligne 36 : les paramètres de la méthode [RestTemplate].springframework. un dictionnaire des données transmises par une opération HTTP POST. lignes 33-42 : appel du service REST par l'objet [RestTemplate] de Spring (ligne 9). Ici le serveur REST utilisé envoie une chaîne JSON.. this. } catch (URISyntaxException ex) { http://tahe. ligne 27 : on vérifie que le serveur REST répond au bout de timeout millisecondes.replace("}". Le convertisseur [StringHttpMessageConverter] n'a pas ce problème. try { 6. On va traiter celle-ci comme une chaîne de caractères sur laquelle on ne fera pas de traitement. Si ce n'est pas le cas. ligne 23 : la méthode [executeRestService] reçoit les paramètres suivants : 1. 47.timeout = millis. les valeurs des variables de l'URL . ""). // on crée l'URI du service distant 3. 4. 45. String url = urlService. la chaîne JSON renvoyée par le serveur .

client. 24. C'est possible car nous utilisons une interface [IMetier] qui englobe l'interface [IMetier] du projet [exemple-07].log(Level. } 28. Il y a une modification à faire dans [MainActivity]. Si on n'y arrive pas c'est que l'URL est incorrecte . } catch (IOException ex) { 25. } • • • • • 9. try { 23. urlService). throw new AleaException(String. ex). Dans [exemple-07] nous avions écrit : // instanciation couche [métier] métier = new Metier(). 16. // configuration 15. // on se connecte au service avec attente maximale définie par 14.SEVERE. } catch (IOException e) { 18. 17. client = new Socket().close(). if (client != null) { 22.connect(new InetSocketAddress(service.getLogger(Dao. throw new AleaException("Le service distant n'a pas répondu assez vite".getName()). ligne 3 : l'URL précédente devient [http://localhost:8080/exemple-08-serve-rest/a/b/n]. Logger.developpez. on essaie de construire un objet URI (Uniform Resource Identifier). la connexion échoue alors on lance une exception.8.com 88/157 . 19. lignes 15-16 : on se connecte à la machine et au port définis par l'URI qui vient d'être construite et on donne un temps maximum de timeout millisecondes pour obtenir la réponse (ligne 16) . client. lignes 5-9 : à partir de cette URL. e). Socket client = null.3. // on se connecte au service 11. 26. timeout). } finally { 20. La couche [Android] Utilisateur Vue Activité Android Couche [metier] Couche [DAO] Serveur Nous gardons la couche [Android] du projet [exemple-07]. } 27. http://tahe. ex). ligne 18 : si pour une raison ou une autre (absence du serveur ou délai d'attente dépassé). 12.format("Format d'URL incorrect [%s]". } 10. service. try { 13.class. // on libère les ressources 21.5 ligne 1 : le paramètre [urlService] est de la forme [http://localhost:8080/exemple-08-serve-rest/{a}/{b}/{n}]. 9.getHost().getPort()). null.

. Adresse IPv4.0. // instanciation couche [métier] 15. . : 14... <groupId>exemples</groupId> http://tahe.: 12. Suffixe DNS propre à la connexion. . .255.. . .com 89/157 . . . Suffixe DNS propre à la connexion. . ..81. .0" encoding="UTF-8"?> 2. . Pour connaître l'adresse IP à mettre en ligne 5. . ligne 15 : on instancie la couche [métier] . : 13. : 19. . @Override 8. 5. il y a désormais une couche [métier] et une couche [DAO] à instancier. 13. .254 Média déconnecté La ligne 11 donne l'adresse IP de votre poste. . ad. 7. 15.34:8080/exemple-08-server-rest".apache. 17.0. : 18. 14. . 20. ouvrez une fenêtre [DOS] et tapez la commande suivante : 1.19. . métier. 16. . protected void onCreate(Bundle savedInstanceState) { 9.. 17.0.Dans [exemple-08-client-rest]. . ligne 17 : on lui injecte l'URL du service REST .19. . . // URL service REST 5. dos>ipconfig 2. 4. . Les dépendances du projet Maven [exemple-08-client-rest] sont les suivantes : 1. 6. <?xml version="1. . . 9. dao.setUrlServiceRest(URL_SERVICE_REST). Passerelle par défaut. .. final private String URL_SERVICE_REST = "172. . public class MainActivity extends FragmentActivity { 2. .. // instanciation couche [dao] 12.0 172. Configuration IP de Windows 4.81. . . IDao dao = new Dao(). 10. . . . ligne 16 : on lui injecte la référence de la couche [DAO] . xsi:schemaLocation="http://maven. . .34 255.developpez. . .0 http://maven. 18.: 11.apache. .0.w3. . 7. . : 10. ligne 13 : on lui fixe son timeout . .setTimeout(1000). . .19.setDao(dao). Carte Ethernet Connexion au réseau local : 8. 11. Le code devient le suivant : 1.xsd"> 4. .univ-angers. 6. . <project xmlns="http://maven. Adresse IPv6 de liaison locale. .0</modelVersion> 5. 3.. .apache.0" xmlns:xsi="http://www. Statut du média. métier.org/POM/4. .fr fe80::698b:455a:925:6b13%4 172. . <modelVersion>4. • • • • • ligne 12 : la couche [DAO] est instanciée . .. ..org/2001/XMLSchema-instance" 3. 3. } 19.org/mavenv4_0_0..org/POM/4.0. . Masque de sous-réseau. . métier = new Metier().. Carte réseau sans fil Wi-Fi : 16.

On obtient l'erreur suivante : http://tahe.RELEASE</spring-android-version> 17.Support Android .1. <android. lignes 42-46 : dépendance sur la bibliothèque JSON Gson .google. </dependency> 35. <spring-android-version>1.plugin. Selon votre configuration d'Eclipse décommentez ou non la ligne 33 ..2</com. 11. <com. 51.version>3. <version>r6</version> 33.code. <groupId>org. <scope>provided</scope> 27.<scope>provided</scope> --> 34. <dependency> 30. </build> 52. <platform.1-SNAPSHOT</version> 8.code. </dependency> 28. <properties> 12.5. <version>${platform. <artifactId>gson</artifactId> 45.version> 16.3. </dependencies> 49. Exécution du client REST Créez un environnement d'exécution pour le projet [exemple-08-client-rest] et exécutez-le sur l'émulateur de tablette.google. </properties> 19.springframework.4 14. lignes 29-34 : dépendance sur la bibliothèque de support d'Android. <dependency> 43.sourceEncoding> 13.0.Gson JSON Processor --> 42. <version>${spring-android-version}</version> 40.developpez.code.3</android.gson</groupId> 44. <!-. .sourceEncoding>UTF-8</project. <groupId>com.plugin.google.gson-version> 18.0.version}</version> 26. <packaging>apk</packaging> 9. 48. <artifactId>exemple-08-client-rest</artifactId> 7.version> 15.Spring Android --> 36.1. <!-. 20.6.NE PAS OUBLIER!!! --> 29. <name>exemple-08-client-rest</name> 10. <artifactId>spring-android-rest-template</artifactId> 39. </dependency> 41. </dependency> 47.Android --> 22. <!-. <artifactId>support-v4</artifactId> 32.version> 4. lignes 36-40 : dépendance sur la bibliothèque Spring-Android . <dependencies> 21.android</groupId> 24. </project> • • • • 9. <groupId>com.gson-version}</version> 46. <!-.1.gson-version>2. <artifactId>android</artifactId> 25.2.com 90/157 .code. <!-. <version>0..google. <version>${com.6 lignes 22-27 : dépendance sur la plateforme Android .android</groupId> 38.build. <groupId>com.google.google. <build> 50.android</groupId> 31. </platform. <dependency> 37. <dependency> 23. <project.build.

developpez. cette règle peut être contournée en ajoutant la ligne 14 ci-dessous dans [Mainactivity] : 1. StrictMode. 15. http://tahe.setUrlServiceRest(URL_SERVICE_REST). on découvre qu'elle est lancée parce qu'on a ouvert une connexion réseau dans le thread de l'activité. // instanciation couche [dao] 6. Vérifiez-le en exécutant l'application. 13. 10.On a une exception. . 7.build()). protected void onCreate(Bundle savedInstanceState) { 3.ThreadPolicy.com 91/157 . // instanciation couche [métier] 9. métier = new Metier(). IDao dao = new Dao().permitAll().Builder(). 8. // accès réseau 14.. Ceci dit. Cette exception vise à renforcer cet usage.setDao(dao). @Override 2.setTimeout(1000). Lorsqu'on se renseigne sur elle. 12. Les bonnes pratiques poussent à ouvrir les connexions réseau dans un thread différent de celui de l'activité. dao. 11. } La ligne 14 va autoriser les connexions réseau dans le thread de l'activité. métier. 5. métier. 4.setThreadPolicy(new StrictMode..

.1. 3.0 7. Inscrivez cette adresse dans [MainActivity] : // URL service REST final private String URL_SERVICE_REST = "192. .y Faites la commande Dos [ipconfig] pour découvrir l'adresse wifi du PC : 1. .168.developpez. . . . Masque de sous-réseau.168. Carte réseau sans fil Wi-Fi : 2.255. : 192. .: 192. Passerelle par défaut. Suffixe DNS propre à la connexion. . . . . . PC1 Tablette Wifi 192. .: fe80::39aa:47f6:7537:f8e1%2 5. .168. Adresse IPv6 de liaison locale. Inhibez le pare-feu de votre PC qui empêche toute connexion venant de l'extérieur et exécutez votre projet sur la tablette. . : 255.255. .1.1.1. . .168.1 La ligne 5 donne l'adresse Wifi du PC. .1. . Utilisez pour cela la clé wifi qu'on vous a donnée. . .com 92/157 .Pour tester l'application avec une vraie tablette. . . . : 4. . .x 192.25 6. . Adresse IPv4. . . http://tahe.168. . . . . vous devez mettre le PC et la tablette sur le même réseau Wifi.25:8080/exemple-08-server-rest". .

Progress. params) Elle reçoit une suite de paramètres de type Params. Pour implémenter la tâche.1 La classe [AsyncTask] D'après la documentation Android : An asynchronous task AsyncTask<Params.com 93/157 .Void> . La vue [Vue_01] la lancera sans attendre sa fin.developpez. Concrètement cela signifie que l'utilisateur peut continuer à interagir avec la vue en même temps que la tâche s'exécute en tâche de fond. C'est le troisième type générique de notre signature AsyncTask<Object. la classe [AsyncTask]. • la méthode [onPostExecute] est exécutée dans le thread de l'activité après l'exécution de la méthode [doInBackGround]. nous ne rendrons aucun résultat. An asynchronous task is defined by 3 generic types. Sa signature est la suivante : protected void onProgressUpdate (Progress.. Elle rend une donnée de type Result. Elle va exécuter les méthodes de la couche [métier] dans des threads différents de celui de l'activité. le type Progress est le deuxième type générique. Elle se déroulera de façon asynchrone.. L'architecture évolue comme suit : Utilisateur Vue Tâche Couche [métier] Couche [DAO] Serveur Activité Android La tâche s'intercale entre la vue et la couche [métier]. • la méthode [onPreExecute] est exécutée dans le thread de l'activité avant l'exécution de la méthode [doInBackGround]. Nous utiliserons la signature AsyncTask<Object. 10.. Result> is defined by a computation that runs on a background thread and whose result is published on the UI thread. Dans la signature AsyncTask<Object. called onPreExecute. La classe [AsyncTask<Params. Result>] peut avoir différentes signatures. • la méthode [onProgressUpdate] permet de publier dans le thread de l'activité l'avancement de la tâche.Object. ici Object . and 4 steps. C'est le premier type générique de notre signature AsyncTask<Object. Elle sera avertie par un événement que la tâche a terminé son travail. Sa signature est la suivante protected abstract Result doInBackground (Params. doInBackground. nous allons étendre une classe Android.Void>.Object. La classe a quatre méthodes principales : • la méthode [doInBackGround] est la méthode exécutée en tâche de fond. Sa signature est la suivante : protected void onPreExecute () C'est dans cette méthode que la [Task] enverra à son boss la notification [WORK_STARTED].Object. onProgressUpdate and onPostExecute.Void>. Nous allons ici la créer dans un autre thread. Progress and Result.Object. Nous. values) Nous n'utiliserons pas cette méthode.10 Exemple-09 : un client REST asynchrone Dans le projet précédent.Void>. called Params. Sa signature est la suivante : http://tahe. La tâche doit avertir la vue lorsqu'elle a terminé son travail. Pour nous ce type sera Object. Progress. nous avons contourné une bonne pratique qui dit qu'on ne doit pas ouvrir une connexion réseau dans le thread de l'activité. La notification est donc envoyée dans le thread de l'UI . La tâche qui s'exécutera dans ce thread sera par ailleurs asynchrone..

3.com 94/157 . public interface ITask { 6.developpez. 2.0.2 Le projet Android Créez un projet [exemple-09-client-rest-asynchrone] par copie du projet [exemple-08-client-rest] : Modifiez les caractéristiques du projet dans [pom.IMetier. donc Void ici. Cette information remontera jusqu'à la vue. import istia. 5. C'est dans cette méthode que la tâche transmettra son résultat à l'objet qui l'a appelée.protected void onPostExecute (Result result) où le type Result est le troisième type générique de la signature AsynTask<Object.tasks] : La tâche asynchrone aura l'interface [ITask] suivante : 1.android. Celle-ci pourra utiliser cette information pour se mettre à jour.1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-09-client-rest-asynchrone</name> 10. Object. Void>. 4.android.3 Les éléments de l'interface asynchrone Nous allons rassembler les éléments de l'interface asynchrone dans un package [istia.st. package istia.st.st. 10.tasks.android.metier. // la tâche asynchrone doit savoir qui l'appelle http://tahe. C'est possible car on sera alors dans le thread de l'activité.xml] de la façon suivante : <groupId>exemples</groupId> <artifactId>exemple-09-client-rest-asynchrone</artifactId> <version>0.

tasks. private Object info. int b = (Integer) params[1]. package istia.. 10. } // pour lui rendre son résultat void setCaller(ICaller caller). public class AleaTask extends AsyncTask<Object. 8. // on récupère les trois paramètres . private IMetier métier. La tâche doit savoir à qui rendre le résultat qu'elle va obtenir.7. 6.metier. 20. import android. private ICaller caller. La méthode [setMetier] de la ligne 11 sert à injecter cette référence . 22. la tâche utilisera la méthode [ICaller]. 14.os. int n = (Integer) params[2]. params) { 17. elle doit savoir à qui elle doit rendre son résultat. // définit une méthode pour récupérer l'info produite par une tâche 5. Pour rendre l'information [info] qu'elle a produite. 3.IMetier. @Override 16.android. La tâche chargée d'obtenir les nombres aléatoires est la tâche [AleaTask] suivante : 1. // le caller 11.callBack(info). Cette interface découle de l'architecture du projet : Utilisateur Vue Tâche Couche [métier] Couche [DAO] Serveur Activité Android La tâche interagit avec la vue et la couche [métier] : • • elle doit avoir une référence sur la couche [métier].st. L'interface [ICaller] est la suivante : 1. public void callBack(Object info). package istia.AsyncTask. Object.tasks. // on appelle la couche [métier] http://tahe. protected Void doInBackground(Object. 10. 15. 21. 12. 11. celle-ci n'attend pas son résultat. 6.android. 5. // la tâche asynchrone doit avoir accès à la couche [métier] void setMetier(IMetier métier). import istia.. 2. // couche [métier] 9.on suppose qu'ils sont corrects 19. 9. } On se rappelle que la tâche [ITask] reçoit un objet [ICaller].android.com 95/157 . Comme elle s'exécute dans un autre thread que la vue qui l'a appelée. 4.developpez. // l'info produite par la tâche 13. 12. 8. Void> implements ITask { 7. 2. int a = (Integer) params[0]. 3.st. // on est dans un autre thread que celui de l'UI 18.st. public interface ICaller { 4. C'est la méthode [setCaller] de la ligne 8 qui sert à injecter cette information.

Cela signifie pour nous que c'est dans cette méthode qu'on doit appeler la couche [métier] . // fin return null. 39. ligne 6 : la tâche étend la classe AsyncTask<Object. 43. 36. 27. } // setters public void setMetier(IMetier métier) { this. 35. C'est dans cette méthode qu'on doit faire les connexions réseau. } • • • • • • • • 10.. 28. 40. C'est pourquoi on n'a pas utilisé de try / catch . n). 38.b]. ligne 28 : la méthode [onPostExecute] est exécutée une fois que la méthode [doInBackground] a terminé son travail.23. ligne 16 : c'est la méthode [doInBackground] qui est exécutée en tâche de fond. ligne 16 : la méthode va recevoir trois paramètres. Celle-ci auparavant appelait directement la couche [métier]. 24. La couche [métier] ne lance pas d'exception.developpez. 34. 33. params] est analogue à la notation [Object[] params]. On reçoit un tableau d'objets . 25. elle s'exécute dans le thread de l'activité Android. L'appel de la tâche dans la vue Revenons à l'architecture de l'application : Utilisateur Vue Tâche Couche [métier] Couche [DAO] Serveur Activité Android La tâche est appelée par la vue. lignes 19-21 : on récupère les trois paramètres a.4 info = métier.callBack(info).getaleas] rend un type [List<Object>] qu'on met dans le type [Object] de la ligne 13. } public void setCaller(ICaller caller) { this. b et n . Object. L'information [info] produite par la méthode [doInBackground] peut alors être utilisée pour mettre à jour l'interface visuelle de l'activité . 42. 26.métier = métier.caller = caller. 31. 30. La méthode [métier. ligne 23 : on appelle la couche [métier]. dans l'ordre les valeurs a. 37.. Void> d'où la présence des méthodes [doInBackground] (ligne 16) et [onPostExecute] (ligne 28) . La notation [Object.getAleas(a. b.com 96/157 . Les changements se font dans la méthode [doExecuter] de la classe [Vue_01] : http://tahe. } protected void onPostExecute(Void result) { // on est dans le thread de l'UI // on rend l'info au caller caller. ligne 31 : on appelle la méthode [callBack] du [ICaller] de la ligne 11 pour rendre l'information [info] produite par la méthode [doInBackground]. Par ailleurs. 41. } ligne 6 : la tâche implémente l'interface [ITask] d'où la présence des méthodes [setMetier] (ligne 35) et [setCaller] (ligne 39) . 29. b et n pour calculer n nombres aléatoires dans l'intervalle [a. 32.

showInfo(info).text1. task. http://tahe.setMetier(activité. // on traite l'info reçue 18. On utilise une technique appelée la classe anonyme. 5.developpez. // on efface les réponses précédentes 10. new String[] {})). txtErrorIntervalle. lignes 15-20 : on lui injecte un objet de type [ICaller] qui indique à la tâche la méthode à rappeler lorsqu'elle a terminé son travail. 14.R.executeOnExecutor(android. } 26. 4.setText(""). if (!isPageValid()) { 7. 25. return.setCaller(new ICaller() { public void callBack(Object info) { // on traite l'info reçue showInfo(info).setAdapter(new ArrayAdapter<String>(activité. 29. // on demande à une tâche asynchrone les nombres aléatoires 12.os. 23. protected void doExecuter() { 2. 6.. // on lui injecte la couche [métier] 22. protected void showInfo(Object info) { 28. txtErrorAleas. 4. b. a.THREAD_POOL_EXECUTOR. 5.getMétier()). // on crée la tâche asynchrone 13. } 20. listRéponses.. 8. // on efface les éventuels msg d'erreur précédents 3..setText(""). AleaTask task = new AleaTask(). // on lui injecte la méthode de rappel 15.La méthode [doExecuter] devient la suivante : 1.setCaller(new ICaller() { 16. nbAleas).. 21. }). task. 27. task. 19. } 9. android. } 30.AsyncTask. 2. La syntaxe est la suivante : new Interface(){ // méthodes d'implémentation de l'interface . ligne 13 : la tâche asynchrone est créée . // on l'exécute 24. } 97/157 .com // on lui injecte la méthode de rappel task. C'est cette méthode qui sera appelée par la tâche asynchrone lorsqu'elle aura fini son travail . . • • • ligne 27 : ce qui était fait précédemment dans la méthode [doExécuter] à réception des nombres aléatoires migre dans la méthode [showInfo]. android. // on teste la validité des saisies 6.R.simple_list_item_1.layout. 11. 3. } donc ici : 1.id. public void callBack(Object info) { 17.

8. nbAleas]. il faut utiliser la syntaxe de la ligne 4 ci-dessus. en lui passant l'objet [info] qu'elle aura créé . android. protected void showInfo(Object info) { // on récupère une liste d'objets @SuppressWarnings("unchecked") List<Object> data = (List<Object>) info. } // on affiche les réponses listRéponses. strings)). 10.7.R.add(o. On caste donc l'objet [info] reçu en paramètre vers un type [List<Object>] . // on l'exécute 4.AsyncTask. b. 2. for (Object o : data) { strings.layout. 5.setAdapter(new ArrayAdapter<String>(activité.simple_list_item_1. 9. Dans notre cas.developpez.R. 12. on lui passe les valeurs qu'elle attend [a. ligne 5 : la méthode [callBack] appelle la méthode [showInfo] de la classe [Vue_01]. Celle-ci est désormais configurée. 7. 3. task. nbAleas).THREAD_POOL_EXECUTOR. task. ligne 4 : on exécute la tâche. nbAleas) On demande à la tâche de s'exécuter.id.com 98/157 . • • lorsque la tâche aura terminé son travail. 11.execute(a. ligne 11 : qu'on associe au [ListView] de l'interface . On ne peut donc avoir qu'une seule tâche asynchrone à la fois.getMétier()). lignes 6-9 : à partir de cette liste d'objets on crée une liste de [String] . Celle-ci attend un tableau de paramètres ou bien une suite de paramètres. }). Elle peut être exécutée . Avec cette syntaxe. b. • • ligne 2 : on injecte la couche [métier] dans la tâche. a.text1. Si on veut pouvoir en avoir plusieurs. un seul thread est alloué pour les tâches asynchrones. deux threads s'exécutent en parallèle : celui de l'activité Android et celui de la tâche asynchrone. Lorsque la ligne 4 ci-dessus a été exécutée. android. alors la vue [Vue_01] devrait implémenter elle-même l'interface [ICaller] et avoir une méthode [void callBack(Object)].executeOnExecutor(android. b. On aurait pu également écrire : task.setMetier(activité. 4.toString()). L'utilisateur peut alors interagir avec l'interface visuelle. elle appellera la méthode de la ligne 3 ci-dessus. } • • • ligne 4 : on sait que la couche [métier] renvoie un type [List<Object>]. http://tahe. // on lui injecte la couche [métier] 2. un thread suffit mais on a voulu montrer la syntaxe du cas plus général. Si on n'utilisait pas la méthode de la classe anonyme. 3. Commentons les dernières lignes de la méthode [doExécuter] : 1.os. // on affiche les objets de la liste List<String> strings = new ArrayList<String>(). Ici. La méthode [showInfo] va être appelée par la tâche lorsque celle-ci aura terminé son travail : 1. Il faudrait mettre ici un indicateur montrant qu'une opération en tâche de fond est en cours. 6.

} La ligne 10 est mise en commentaires... 7. // accès réseau dans le thread de l'UI 10.build()).developpez. métier. . métier = new Metier().5 Modification de l'activité La classe [MainActivity] est modifiée pour ne plus autoriser les connexions réseau dans le thread de l'activité : 1. 11.Builder(). http://tahe.10.setThreadPolicy(new StrictMode.setUrlServiceRest(URL_SERVICE_REST). // instanciation couche [métier] 5.permitAll(). 8.6 Exécution du client REST asynchrone Créez un environnement d'exécution pour le projet [exemple-09-client-rest-asynchrone] et exécutez-le à la fois sur l'émulateur de tablette et sur la tablette elle-même si vous avez un réseau wifi. 6. @Override 2. protected void onCreate(Bundle savedInstanceState) { 3. 10. 4. 9.setDao(dao).com 99/157 .ThreadPolicy. métier. // StrictMode.

developpez. nous n'avons rien prévu pour annuler la tâche asynchrone si elle tarde à répondre.11 Exemple-10 : annulation d'une tâche asynchrone Dans le projet précédent. requestWindowFeature(Window.1 L'activité [MainActivity] L'activité [MainActivity] évolue pour inclure une image d'attente : 1. Modifiez les caractéristiques du fichier [pom.FEATURE_INDETERMINATE_PROGRESS). Créez un projet [exemple-10-client-rest-asynchrone-annuler] en dupliquant le projet [exemple-10-client-rest-asynchrone]. l'utilisateur peut continuer à interagir avec l'interface visuelle on peut lui présenter un bouton [Annuler] qu'il peut utiliser pour annuler la tâche qu'il a lancée. super. on peut lui proposer également un indicateur visuel qui montre qu'une tâche de fond est en cours.com 100/157 .xml] de la façon suivante : <groupId>exemples</groupId> <artifactId>exemple-10-client-rest-annuler</artifactId> <version>0. 5. Le nouveau projet répond à ces deux demandes. C'est la ligne 6 qui installe une image d'attente. Par ailleurs. // création de la vue 8. // classique 4.onCreate(savedInstanceState). // il faut installer le sablier avant de créer la vue 6.0. Puisque lorsqu'elle est lancée. @Override 2. pour l'instant cachée.activity_main).1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-10-client-rest-annuler</name> 11. en haut et à droite de la barre d'état réservée à l'activité : http://tahe. setContentView(R.layout. 7. protected void onCreate(Bundle savedInstanceState) { 3.

11.Pour afficher l'image on écrit : [Activity]. 16. 8. le bouton [Annuler] qui annulera la tâche asynchrone lancée par la vue. 4. 9. 15. où [Activity] désigne l'activité qui affiche l'image.com 101/157 . 6.2 La vue XML [vue_01] La vue XML [vue_01] doit intégrer un bouton supplémentaire. 7. 13. 5. <Button android:id="@+id/btn_Executer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/txt_a" android:layout_marginTop="20dp" android:text="@string/btn_executer" /> <Button android:id="@+id/btn_Annuler" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/txt_a" android:layout_marginTop="20dp" http://tahe. seul l'un d'eux sera visible. 3.setProgressBarIndeterminateVisibility(true). A un moment donné.setProgressBarIndeterminateVisibility(false). Le code XML des deux boutons est le suivant : 1. 14. Nous allons placer le bouton [Annuler] par-dessus le bouton [Exécuter]. 12. 10. 11.developpez. 2. et pour la cacher : [Activity].

9. private Button btnExecuter. private AleaTask task. 22. • • • ligne 5 : la référence du bouton [Annuler] . // les éléments de l'interface visuelle 4. doExecuter(). http://tahe. public View onCreateView(LayoutInflater inflater.. Bundle savedInstanceState) { 12. 18. . }). un seul des deux soit visible.3 Le fragment [Vue_01] Le code du fragment [Vue_01] associé à la vue XML [vue_01] évolue de la façon suivante : 1. public void onClick(View arg0) { 24. 20. 15.. lignes 22-28 : on gère le clic sur le bouton [Annuler] . // la tâche asynchrone 8. 5. } 29.findViewById(R. btnExecuter = (Button) view.findViewById(R.xml] : <string name="btn_executer">Exécuter</string> <string name="btn_annuler">Annuler</string> Comme le bouton [Annuler] a été obtenu par copier / coller du bouton [Exécuter]. android:text="@string/btn_annuler" /> Les lignes 10-17 sont d'abord obtenues par copier / coller des lignes 1-8 puis on change les éléments suivants : • • ligne 11 : l'identifiant du bouton sera [btn_Annuler] . btnAnnuler. On fera en sorte qu'à un moment donné.com 102/157 .setOnClickListener(new OnClickListener() { 23. public class Vue_01 extends Fragment { 2. } 19.17. // bouton Exécuter 14.cancel(true). 28. task. private Button btnAnnuler. btnAnnuler = (Button) view. // bouton Annuler 21.setOnClickListener(new OnClickListener() { 16. Ils sont superposés. btnExecuter. // on annule la tâche 25. 10.btn_Annuler). ViewGroup container. }).id. @Override 11. ils occupent la même place dans la vue. ligne 21 : on récupère cette référence . 6. cancelWaiting().developpez. . 13. 26. public void onClick(View arg0) { 17.id.. ligne 17 : son texte sera fourni par la chaîne [btn_annuler] dans le fichier [res/values/strings. 11.btn_Executer). 7. // on arrête l'attente 27. 3.

setVisibility(View.setVisibility(View. on commence l'attente du résultat ligne 6.Object>]. task. La méthode [doExécuter] référencée ligne 17 évolue comme suit : 1. // on met le sablier 6. 5. .INVISIBLE). // on termine l'attente 3.setProgressBarIndeterminateVisibility(false).VISIBLE). beginWaiting(). } Enfin. @SuppressWarnings("unchecked") 6. 7. a. 8.setVisibility(View. activité. } La méthode [cancelWaiting] appelée lorsqu'on clique sur le bouton [Annuler] est la suivante : 1. btnAnnuler. activité. La classe [AsyncTask] a une méthode [cancel] qui permet d'annuler la tâche. la tâche s'arrête immédiatement. 3. // le bouton [Annuler] remplace le bouton [Exécuter] 3.• • ligne 25 : on annule la tâche asynchrone référencée ligne 8.. 5. protected void showInfo(Object info) { 2. // on commence l'attente 6. b. btnExecuter...developpez. Cette tâche étend la tâche [AsyncTask<Object.com 103/157 . 5. 7.VISIBLE).setVisibility(View. private void beginWaiting() { 2. btnAnnuler.setProgressBarIndeterminateVisibility(true). A faux. on laisse sa méthode [doInBackground] se terminer mais la méthode [onPostExecute] ne sera pas exécutée . 4. List<Object> data = (List<Object>) info.executeOnExecutor(android. La méthode [beginWaiting] est la suivante : 1. Cette méthode admet comme paramètre un booléen : à vrai.THREAD_POOL_EXECUTOR.Void. // le bouton [Exécuter] remplace le bouton [Annuler] 3. btnExecuter.os. protected void cancelWaiting() { 2.INVISIBLE). la méthode [showInfo] qui affiche le résultat envoyé par la tâche asynchrone évolue comme suit : 1. . // on exécute la tâche 4. cancelWaiting(). nbAleas). 7. L'utilisateur peut continuer à interagir avec l'interface.. 4. 7. ligne 27 : on annule l'attente. Pour voir l'action du bouton [Annuler] modifiez la couche [métier] de votre serveur REST de la façon suivante : http://tahe. C'est une attente visuelle. } Après avoir lancé la tâche asynchrone ligne 4.AsyncTask. } Créez un environnement d'exécution pour le projet [exemple-10-client-rest-asynchrone-annuler] et exécutez-le. // on enlève le sablier 6. protected void doExecuter() { 2. // on récupère une liste d'objets 5. 4.

List<Object> réponses = new ArrayList<Object>(). } 13.printStackTrace(). } catch (InterruptedException e) { 10. int b. 16. // qqs vérifications 17. • lignes 7-12 : on arrête artificiellement le thread de la couche [métier] pendant 5 secondes.. @Override 5. // on s'arrête 5 secondes 7.com 104/157 . // TODO Auto-generated catch block 11. 12. try { 8.developpez. e. public class Metier implements IMetier { 3. // la liste des objets 15. 14. public List<Object> getAleas(int a. Du coup la tâche asynchrone ne rendra son résultat qu'au bout de 5 secondes ce qui permet de voir le bouton [Annuler] ainsi que le sablier.sleep(5000). int n) { 6. Thread. 4.. @Service 2. . 9.1. http://tahe.

12 Exemple 11 : gestion de plusieurs tâches asynchrones Dans l'exemple précédent.0.0</modelVersion> 2. <version>1. Pour gérer la fin de l'attente. Pour des cas plus complexes. <groupId>exemples</groupId> 3.1 La couche [métier] Clients Couche [web / Rest] Couche [metier] Spring / Tomcat http://tahe. nous allons en gérer plusieurs. nous allons simplement compter les réponses envoyées par les tâches asynchrones.0-BUILD-SNAPSHOT</version> 12. Dans cet exemple. Nous gardons le même projet que précédemment mais au lieu que les N nombres aléatoires soient générés par une seule tâche asynchrone. <artifactId>exemple-11-server-rest</artifactId> 4. nous n'avons géré qu'une tâche asynchrone. Nous commençons par dupliquer le projet [exemple-08-server-rest] dans [exemple-11-server-rest] : Modifiez les identifiants du projet dans [pom.1. une méthode différente est proposée dans [http://tahe. <name>exemple-11-server-rest</name> 5.com 105/157 .com/android/avat/].xml] de la façon suivante : 1. 12.1 Le serveur [REST] Notre serveur REST doit évoluer.developpez.developpez. On ne lui demande plus N nombres aléatoires d'un coup mais un seul à chaque fois. <packaging>war</packaging> 6. Lorsqu'on en aura N. Cette méthode marche bien lorsque la vue connaît les tâches lancées (elle peut alors les annuler) et le nombre de résultats attendus (elle peut alors les compter). nous allons utiliser N tâches asynchrones qui vont chacune générer un nombre aléatoire. <modelVersion>4.0. on arrêtera l'attente.

// // TODO Auto-generated catch block 17. } L'unique méthode de cette interface rend un nombre aléatoire dans l'intervalle [a.developpez.stereotype. int b) { 12. public interface IMetier { 4.b] 38. throw new AleaException("Dans l'intervalle [a. import java. 7. return a + random. if (b < 0) { 25. 4. 29.metier. int nombre = random. public class Metier implements IMetier { 9. 34. //try { 14. //} catch (InterruptedException e) { 16. // rend un nombre aléatoire dans l'intervalle [a. S'il n'est pas généré ici. 18. le nombre aléatoire est généré ligne 38. @Service 8. 32.Random. 5. throw new AleaException("Exception aléatoire"). package istia.st. // Thread. 39. } 30. } 40. Random random = new Random(). 26. 5. public int getAlea(int a. } • • lignes 13-18 : on arrête artificiellement pendant 5 secondes le thread de la couche [métier]. import org. if (nombre == 0) { 35. 3. // on s'arrête 5 secondes 13.Service. throw new AleaException("Le nombre a de l'intervalle [a. //} 19. package istia.nextInt(b . 15.st. // on génère une exception aléatoire 1 fois / 3 33. public int getAlea(int a.aleas. // sinon on rend un nombre aléatoire entre deux bornes [a. L'implémentation [Metier] de cette interface est la suivante : 1. on doit avoir a< b"). } 27.b] doit être supérieur à 0"). 2. // e. if (a >= b) { 28. 2.printStackTrace(). c'est qu'une exception de type [AleaException] a été lancée entre-temps. } else { 37.nextInt(3). 36. if (a < 0) { 22. toujours pour que l'utilisateur puisse voir le bouton [Annuler] .com 106/157 . 6.b].util.metier. throw new AleaException("Le nombre b de l'intervalle [a.aleas. } 24.sleep(5000).b]. 10. 20.L'interface [IMetier] évolue de la façon suivante : 1. http://tahe. // vérifications 21. @Override 11. int b).b] doit être supérieur à 0"). 3. // on génère le nombre aléatoire 31. } 41. 7.springframework. 23.a + 1).b] 6.

String. b).12. String> map = new HashMap<String..getAlea(a. "1"). } catch (AleaException ex) { 24.valueOf(alea)). 9. JsonMappingException. import istia. if (aleaException == null) { 30.st. 23. private IMetier metier. // nombre aléatoire 14. 18.AleaException..aleas. // on va renvoyer la chaîne Json d'un dictionnaire 27. 4.GET. } 36. map. 6.developpez. } 26.put("alea". 5. 25. 34. map. package istia. AleaException aleaException = null. Map<String.1.com 107/157 . map. 31. method = RequestMethod. } else { 33. // on utilise la couche métier 19. 20. 28. @Inject 11.metier. produces = "text/plain. alea = metier. map. // exception ? 29. // couche métier 10.put("msg". 35. @PathVariable("b") int b) throws JsonGenerationException.aleas. @RequestMapping(value = "/{a}/{b}". 32. "0"). aleaException. public class AleaController { 8.charset=UTF-8") 15. try { 22. 13. String>().2 Le contrôleur Spring MVC Clients Couche [web / Rest] Couche [metier] Spring / Tomcat Le contrôleur [AleaController] devient le suivant : 1. public String getAlea(@PathVariable("a") int a. aleaException = ex. 12. // on rend la chaîne Json du dictionnaire http://tahe.getMessage()). IOException { 17. @ResponseBody 16. int alea = 0. @Controller 7.put("erreur". 3. 2. .rest. 21.put("erreur".st.

} 40.com 108/157 . vous n'obtenez pas le caractère accentué de [aléatoire].xml] : <modelVersion>4."msg":"message de l'exception"} s'il y a eu une erreur. Le client Android du serveur REST Dupliquez le projet [exemple-10-client-rest-asynchrone-annuler] dans [exemple-11-client-rest-asynchrones] : Modifiez les caractéristiques du projet Maven dans [pom.2 l'URL présentée est [localhost:8080/exemple-08-server-rest]. lignes 19-25 : le nombre aléatoire dans l'intervalle [a.writeValueAsString(map). lignes 27-35 : le dictionnaire de la réponse est construit . On obtient deux sortes de résultats : Vous pouvez rencontrer deux problèmes : • • 12.37. Lorsqu'on importe un projet. } • ligne 14 : l'URL attendue est désormais simplement [/a/b] pour demander un nombre aléatoire dans l'intervalle [a. ligne 37 : on renvoie la chaîne JSON de ce dictionnaire . Vérifiez que votre projet est en UTF-8. return new ObjectMapper().0. Exécutez cette application web (clic droit sur projet / Run as / Run on server). Supprimez le projet [clic droit / Delete] sans supprimer le dossier puis réimportez le projet . Eclipse a gardé l'URL du projet [exemple-08].0</modelVersion> http://tahe.b] est demandé à la couche [métier] . Eclipse le met par défaut en [Cp1252] ."alea":"127"} s'il n'y a pas eu d'erreur ou bien {"erreur":"1".developpez.b]. 38. 39. Le contrôleur rendra son résultat sous la forme de deux chaînes JSON : {"erreur":"0". • • • • ligne 16 : il n'y a plus que deux paramètres nommés a et b .

1-SNAPSHOT</version> <packaging>apk</packaging> <name>exemple-11-client-rest-asynchrones</name> 12. public String executeRestService(String method.dao.com 109/157 . Object request.2. String> paramètres).0.1 La couche [DAO] Utilisateur Vue Tâche asynchrone Couche [metier] Couche [DAO] Serveur Activité Android L'interface [IDao] est actuellement la suivante : 1.2 La couche [métier] Utilisateur Vue Tâche asynchrone Couche [metier] Couche [DAO] Serveur Activité Android http://tahe. 2. String urlService. } La méthode [executeRestService] rend la chaîne JSON envoyée par le serveur REST. 5. 12. 9. 8.util. 4. 3. Cela nous convient. package istia. public void setTimeout(int millis).developpez. 7.Map. public interface IDao { 6.st.android. Map<String.<groupId>exemples</groupId> <artifactId>exemple-11-client-rest-asynchrones</artifactId> <version>0. import java.2. La couche [DAO] reste inchangée.

fromJson(réponse. paramètres. int b). 14. 4. 2. public void setDao(IDao dao). try { 11.format("http://%s/{a}/{b}".get("msg"). int b. 10.put("b". // paramètres service REST 5. La classe d'implémentation [Metier] évolue alors comme suit : 1.equals("0") && alea != null) { 22.st. réponse = dao. public List<Object> getAleas(int a. String alea = data.com 110/157 . urlService.executeRestService("get". 6. String>>() { 15. String. String>(). // adresse du service REST 3. null. } 24. }. 7. // on exploite la réponse JSON 14. public void setUrlServiceRest(String url). 7. public interface IMetier { 8. import istia. import java. paramètres. String réponse = null. return Integer. String urlService = String.metier. int b) { 2. String msg = data.developpez. Map<String. if (erreur != null && erreur.getType()).get("alea"). 4.on récupère un [String] 12.util. 8. int n).st. 12. // exécution service . String> data = new Gson().android. } La méthode [getAleas] de la ligne 9 ne convient plus. 26. 10.valueOf(a)). new TypeToken<Map<String.valueOf(alea). } 27.equals("1") && msg != null) { 25. 6. 18. 11. public Object getAlea(int a. 5. String> paramètres = new HashMap<String.android. return msg. 13. 3.on lance une exception http://tahe. package istia. 20.put("a". urlServiceRest). paramètres).L'interface [IMetier] est actuellement la suivante : 1. if (erreur != null && erreur. 19. Map<String. 16. Elle permet d'obtenir soit le nombre aléatoire soit le message d'erreur envoyé par le serveur REST.get("erreur"). 13.valueOf(b)). 23. String. // on traite les différents cas 21. Elle est désormais remplacée par la méthode [getAlea] suivante : public Object getAlea(int a. // pas normal . // on récupère les éléments du dictionnaire 17.List. 9. String erreur = data.dao. // exécution service [DAO] 9.IDao.

36.getCause(). } On se rappelle que la chaîne JSON envoyée par le serveur REST a deux formes : {"erreur":"0". 29. lignes 22-24 : le cas où on a récupéré le nombre aléatoire.developpez. ni un message d'erreur alors la réponse est invalide. La tâche asynchrone Utilisateur Vue Tâche asynchrone Couche [metier] Couche [DAO] Serveur Activité Android http://tahe. ligne 29 : si on a reçu ni un nombre aléatoire."alea":"127"} s'il n'y a pas eu d'erreur ou bien {"erreur":"1". throw new AleaException(String.format("Le serveur a renvoyé une réponse invalide [%s]". String> de clés ["erreur". ligne 30 : on attrape ici toutes les exceptions qui ont pu se produire dans le catch. On rend un type [String] .28."msg"] pour la seconde. 38. réponse)). messages. Ces chaînes JSON peuvent être transformées en dictionnaires Map<String. On renvoie comme résultat un objet List<String> constitué des messages d'erreur tirés de la pile d'exceptions. th = th. List<String> messages = new ArrayList<String>(). 32."msg":"message de l'exception"} s'il y a eu une erreur.2. while (th != null) { 34. 35."alea"] pour la première chaîne et ["erreur". 33. } catch (Exception ex) { 30. } 39.3 la chaîne JSON reçue est transformée en dictionnaire ligne 15 . // cas d'erreur . } 37. On lance une exception pour l'indiquer . • • • • • • 12. lignes 25-27 : le cas où on a reçu un message d'erreur.add(th. lignes 18-20 : on récupère les valeurs associée aux trois clés possibles .getMessage()).on récupère les msg d'erreur de la pile d'exceptions 31. On rend un type [Integer] . Throwable th = ex. return messages.com 111/157 .

// fin 10. 7. 6. new String[] {})). 5.simple_list_item_1.setAdapter(new ArrayAdapter<String>(activité. protected Void doInBackground(Object. android.getAlea(a. int b = (Integer) params[1]. 14. info = métier. // on est dans un autre thread que celui de l'UI 4. // on teste la validité des saisies if (!isPageValid()) { return.setText(""). ?>[] tasks. protected void doExecuter() { // on efface les éventuels msg d'erreur précédents txtErrorAleas. Sa méthode [doInBackground] évolue comme suit : 1.. } • • 12. 15. 11. 11. 12. 2. return null. 6. private int nbInfos. 8. 10. b). android.id. Le fragment [Vue_01] Utilisateur Vue Tâche asynchrone Couche [metier] Couche [DAO] Serveur Activité Android Le code de la classe [MainActivity] évolue comme suit : 1. // on récupère les deux paramètres .developpez..layout. } // on efface les réponses précédentes listRéponses.text1. http://tahe. // les réponses List<String> réponses = new ArrayList<String>(). params) { 3. txtErrorIntervalle. 9. les valeurs a et b de l'intervalle [a.4 lignes 5-6 : la vue ne passe plus que deux paramètres à la tâche asynchrone.on suppose qu'ils sont corrects 5. @Override 2. int a = (Integer) params[0]. ligne 8 : c'est la couche [métier] qui génère le nombre aléatoire . 3.setText(""). 13.R. // on appelle la couche [métier] 8.R. 16. 4.2.b] .com 112/157 .La tâche asynchrone [AleaTask] va être appelée de façon répétée. 17. 9. // les tâches asynchrones private AsyncTask<?. ?. 7.

getMétier()). lignes 22-37 : on crée les [nbAleas] tâches et on les lance.R. 36. android.toString()).toString()). ligne 3 : on va compter le nombre d'informations rendues par les tâches aléatoires afin de savoir si l'attente est terminée ou non .executeOnExecutor(android. 20.setMetier(activité. i++) { // on crée la tâche n° i AleaTask task = new AleaTask(). ligne 5 : on va cumuler les réponses des différentes tâches dans une liste. 24. 23. if (nbInfos == nbAleas) { 5. // liste d'exceptions 14. 39. 29. // on lui injecte la méthode de rappel task.AsyncTask. réponses. listRéponses. Celle-ci sera visualisée par le [ListView] de l'interface . 40. nbInfos = 0. réponses)).18. // on ajoute l'information à la liste des réponses 20. for (Object object : (List<?>) info) { 15. android. 31.THREAD_POOL_EXECUTOR.id. 38.add(object. 34. b). i < nbAleas. } 23. 33. protected void showInfo(Object info) { 2. On incrémente leur compteur . 21. 27. ligne 2 : les tâches asynchrone lancées par la vue seront mémorisées dans un tableau afin de pouvoir les annuler . } • • • • • • // on demande à une tâche asynchrone les nombres aléatoires // on crée les tâches asynchrones tasks = new AleaTask[nbAleas].add("bizarre : " + info. 22. } }). // bizarre 19. nbInfos++.layout. // on lui injecte la couche [métier] task. 26.toString()). réponses. if (info instanceof List<?>) { 13. // on termine l'attente 6.com 113/157 . 28. if (info instanceof Integer || info instanceof String) { 9.simple_list_item_1. 11. cancelWaiting(). } 17. 7. // on ajoute l'information à la liste des réponses 10. } else { 12. 21.setCaller(new ICaller() { public void callBack(Object info) { // on traite l'info reçue showInfo(info). } 22.text1. 25. // on affiche les réponses 24. } • ligne 3 : on a reçu une information. 37. } // on commence l'attente beginWaiting(). 16. réponses. 32.add(info. 4. 30. http://tahe. 25. ligne 20 : le tableau des tâches est créé .setAdapter(new ArrayAdapter<String>(activité. for (int i = 0. // a-t-on terminé ? 3. Chacune va générer un nombre aléatoire .developpez. a. tasks[i] = task. // on l'exécute task.os. } else { 18. La méthode qui affiche l'information reçue évolue comme suit : 1. } 8. 35. ligne 21 : on remet à zéro le compteur des informations rendues par les tâches . 19.R.

34:8080/exemple-11-server-rest". Dans les deux cas. 12.on reçoit trois types d'objets de type [Integer]. • • • • 12.25:8080/exemple-11-server-rest". // URL service REST 2.6 Exécution Créez un environnement d'exécution pour le projet [exemple-11-server-rest] et exécutez-le. Adaptez ces adresses à votre configuration. lignes 12-16 : on s'occupe du troisième type .developpez.19.5 L'activité [MainActivity] Utilisateur Vue Tâche asynchrone Couche [metier] Couche [DAO] Serveur Activité Android L'activité change peu.168. On doit simplement changer l'URL du serveur REST : 1.1. [String] et [List<String>]. Ligne 9. ligne 17 : cas improbable ligne 24 : on associe la liste [réponses] au [ListView] pour qu'il l'affiche. //final private String URL_SERVICE_REST = "172. faite [ipconfig] dans une fenêtre DOS pour avoir l'adresse IP de votre PC. L'adresse de la ligne 2 est pour l'émulateur de tablette. final private String URL_SERVICE_REST = "192. http://tahe. on s'occupe des deux premiers .2.2. 3.81. Celle de la ligne 3 est pour la tablette lorsqu'elle est connectée au réseau wifi.com 114/157 .

13 Exemple-12 : composants de saisie de données Nous allons écrire un nouveau projet pour présenter quelques composants usuels dans les formulaires de saisie de données : 13.com 115/157 . Achevé.2 La vue XML du formulaire http://tahe.developpez.1 Le projet Android Construisez un projet Maven Android [exemple-12-formulaire]. il ressemblera à ceci : 13.

android:layout_width="wrap_content" 13. android:layout_alignParentLeft="true" 15. android:text="@string/formulaire_radioButton" 48. android:layout_width="wrap_content" 24. android:layout_marginTop="50dp" 28.xml] : 1. 50. <Button 22. 21. 30. android:layout_width="match_parent" 4. android:layout_marginTop="30dp" 47. android:id="@+id/formulaireButtonValider" 23. android:layout_below="@+id/textViewFormulaireTitre" 36. android:layout_height="wrap_content" 44. <?xml version="1. android:id="@+id/textViewFormulaireTitre" 12.android.La vue XML du formulaire est dans [formulaire. android:text="@string/formulaire_titre" 19. android:layout_below="@+id/textViewFormulaireCheckBox" 46. android:textSize="20sp" /> 39. android:layout_marginTop="30dp" 37. android:text="@string/formulaire_checkbox" 38. <TextView 31.com/apk/res/android" 3. android:layout_height="wrap_content" 14. android:layout_height="wrap_content" 34. android:id="@+id/textViewFormulaireCheckBox" 32. <TextView 11. android:layout_width="wrap_content" 33. android:layout_marginLeft="50dp" 17. 6. android:layout_marginTop="30dp" 18. <RelativeLayout 7. android:layout_height="match_parent" > 5.developpez.0" encoding="utf-8"?> 2. android:layout_alignParentTop="true" 16. 40. android:layout_width="wrap_content" 43. android:text="@string/formulaire_valider" /> 29. <TextView http://tahe. android:layout_width="match_parent" 8. android:textSize="20sp" /> 49. <ScrollView xmlns:android="http://schemas. android:layout_height="wrap_content" > 9. 10. android:layout_below="@+id/TextViewFormulaireCombo" 27. android:layout_alignLeft="@+id/textViewFormulaireTitre" 35. android:id="@+id/textViewFormulaireRadioButton" 42. android:layout_alignLeft="@+id/TextViewFormulaireCombo" 26.com 116/157 . android:layout_alignLeft="@+id/textViewFormulaireCheckBox" 45. android:textSize="30sp" /> 20. android:layout_height="wrap_content" 25. <TextView 41.

100. 109. 83. 93. 72. 112. 69. 55. 104. 94. 92. 108. 65. 90.com android:id="@+id/textViewFormulaireSeekBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textViewFormulaireRadioButton" android:layout_below="@+id/textViewFormulaireRadioButton" android:layout_marginTop="30dp" android:text="@string/formulaire_seekBar" android:textSize="20sp" /> <TextView android:id="@+id/textViewFormulaireEdtText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textViewFormulaireSeekBar" android:layout_below="@+id/textViewFormulaireSeekBar" android:layout_marginTop="30dp" android:text="@string/formulaire_saisie" android:textSize="20sp" /> <TextView android:id="@+id/textViewFormulaireBool" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textViewFormulaireEdtText" android:layout_below="@+id/textViewFormulaireEdtText" android:layout_marginTop="30dp" android:text="@string/formulaire_bool" android:textSize="20sp" /> <TextView android:id="@+id/textViewFormulaireDate" android:layout_width="wrap_content" android:layout_height="200dp" android:layout_alignLeft="@+id/textViewFormulaireBool" android:layout_below="@+id/textViewFormulaireBool" android:layout_marginTop="50dp" android:gravity="center" android:text="@string/formulaire_date" android:textSize="20sp" /> <TextView android:id="@+id/textViewFormulaireMultilignes" android:layout_width="150dp" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireTitre" android:layout_alignParentTop="true" android:layout_marginLeft="400dp" android:layout_toRightOf="@+id/textViewFormulaireTitre" android:text="@string/formulaire_multilignes" android:textSize="20sp" /> <TextView android:id="@+id/textViewFormulaireTime" android:layout_width="wrap_content" android:layout_height="200dp" android:gravity="center" android:layout_alignLeft="@+id/textViewFormulaireMultilignes" android:layout_below="@+id/textViewFormulaireMultilignes" android:layout_marginTop="50dp" android:text="@string/formulaire_time" android:textSize="20sp" /> <TextView 117/157 . 110. 87. 75. 68. 82. 67. 58. 102. 76. 73. 77. 61. 56. 63. 101. 85. 74. 57. 81. 62. 66. 89. 64. 84. 71.developpez. 52. 80. 53. 99. 113. 88. 97. 98. 78. 60. 111. 79. 96. 106. 103.51. 107. 91. 95. http://tahe. 86. 59. 105. 70. 54.

138. http://tahe. 141. 119. 148. 127. 172. 142. 170. 120. 139. 162. 140. 147. 152. 125. 143. 136. 122. 173. 174. 135. 150. 151.114. 158. 155. 126. 167. 159. 144. 161. 117. 133. 153. 129. 128. 154. 134. 163.developpez. 130. 169. 146. 131.com android:id="@+id/TextViewFormulaireCombo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textViewFormulaireTime" android:layout_below="@+id/textViewFormulaireTime" android:layout_marginTop="50dp" android:text="@string/formulaire_combo" android:textSize="20sp" /> <CheckBox android:id="@+id/formulaireCheckBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireCheckBox" android:layout_marginLeft="100dp" android:layout_toRightOf="@+id/textViewFormulaireCheckBox" android:text="@string/formulaire_checkbox1" /> <RadioGroup android:id="@+id/formulaireRadioGroup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireRadioButton" android:layout_alignLeft="@+id/formulaireCheckBox1" android:orientation="horizontal" > <RadioButton android:id="@+id/formulaireRadioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/formulaire_radiobutton1" /> <RadioButton android:id="@+id/formulaireRadioButton2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/formulaire_radionbutton2" /> <RadioButton android:id="@+id/formulaireRadionButton3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/formulaire_radiobutton3" /> </RadioGroup> <SeekBar android:id="@+id/formulaireSeekBar" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireSeekBar" android:layout_alignLeft="@+id/formulaireCheckBox1" /> <EditText android:id="@+id/formulaireEditText1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireEdtText" android:layout_alignLeft="@+id/formulaireCheckBox1" android:ems="10" android:inputType="text" > </EditText> <Switch 118/157 . 121. 176. 137. 149. 171. 164. 160. 118. 145. 115. 124. 166. 175. 123. 132. 116. 157. 168. 165. 156.

177. 180. 195. 230. 228. 216. 224. 229. 205. 182. 211. 221. 225. 226. android:id="@+id/formulaireSwitch1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireBool" android:layout_alignLeft="@+id/formulaireCheckBox1" android:text="@string/formulaire_switch" android:textOff="Non" android:textOn="Oui" /> <TimePicker android:id="@+id/formulaireTimePicker1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/textViewFormulaireTime" android:layout_alignLeft="@+id/formulaireEditTextMultiLignes" /> <EditText android:id="@+id/formulaireEditTextMultiLignes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireMultilignes" android:layout_alignBottom="@+id/textViewFormulaireMultilignes" android:layout_marginLeft="100dp" android:layout_toRightOf="@+id/textViewFormulaireMultilignes" android:ems="10" android:inputType="textMultiLine" > </EditText> <Spinner android:id="@+id/formulaireDropDownList" android:layout_width="200dp" android:layout_height="50dp" android:layout_alignBottom="@+id/TextViewFormulaireCombo" android:layout_alignLeft="@+id/formulaireEditTextMultiLignes" > </Spinner> <DatePicker android:id="@+id/formulaireDatePicker1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/textViewFormulaireDate" android:layout_alignLeft="@+id/formulaireCheckBox1" > </DatePicker> <TextView android:id="@+id/textViewSeekBarValue" android:layout_width="30dp" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textViewFormulaireSeekBar" android:layout_marginLeft="30dp" android:layout_toRightOf="@+id/formulaireSeekBar" android:text="" /> </RelativeLayout> </ScrollView> Les principaux composants du formulaire sont les suivants : • ligne 2 : un layout [ScrollView] vertical. 190. 209. 184. 223. 214. 192. 219. 204. 210. 179. 231. 196. 215. 202. 197. 218.com 119/157 . 199. 206. http://tahe. 187. Il permet de présenter un formulaire plus grand que l'écran de la tablette. 191. 185. 186. On obtient la totalité du formulaire par défilement . 207.developpez. 189. 227. 194. 183. 208. 203. 181. 200. 201. 217. 213. 220. 198. 222. 212. 178. 188. 193.

• lignes 123-130 : une case à cocher • lignes 132-157 : un groupe de trois boutons radio • lignes 159-164 : une barre de recherche • lignes 166-174 : une boîte de saisie • lignes 176-184 : un switch oui / non • lignes 186-191 : une boîte de saisie de l'heure • lignes 193-203 : une boîte de saisie multi-lignes • lignes 205-211 : une liste déroulante • lignes 213-219 : une boîte de saisie d'une date • tous les autres composants sont des [TextView] qui affichent des textes. http://tahe.com 120/157 .developpez.

13. <string name="formulaire_multilignes">Champ de saisie multilignes</string> 15. <string name="formulaire_radiobutton1">1</string> 20. <string name="formulaire_titre">Formulaire</string> 7. <string name="formulaire_seekBar">Seek Bar</string> 10. <string name="formulaire_valider">Valider</string> 24.4 Le fragment du formulaire La classe [FormulaireFragment] est la suivante : 1.st. <resources> 3. <string name="formulaire_bool">Booléen</string> 12.3 Les chaînes de caractères du formulaire Les chaînes de caractères du formulaire sont définies dans le fichier [res / values / strings. <string name="action_settings">Settings</string> 6. http://tahe. 4. </resources> 13.android. <string name="formulaire_checkbox1">1</string> 18. <string name="formulaire_saisie">Champ de saisie</string> 11. <string name="formulaire_checkbox">Cases à cocher</string> 8. <string name="formulaire_combo">Liste déroulante</string> 17. <string name="formulaire_date">Date</string> 13. <?xml version="1.com 121/157 .0" encoding="utf-8"?> 2. <string name="formulaire_listview">Liste</string> 16. <string name="app_name">exemple-12</string> 5. 25.developpez. 2. <string name="formulaire_switch"></string> 23. <string name="formulaire_radionbutton2">2</string> 21. package istia. <string name="formulaire_radiobutton3">3</string> 22. <string name="formulaire_checkbox2">2</string> 19. <string name="formulaire_radioButton">Boutons Radio</string> 9.xml] suivant : 1. <string name="formulaire_time">Heure</string> 14.

// un fragment est une vue affichée par un conteneur de fragments 8.formulaireRadioButton1).formulaire. 39. 34. private EditText multiLignes. 58. ViewGroup container.findViewById(R. final TextView seekBarValue = (TextView) rootView. public class FormulaireFragment extends Fragment { 9. // les boutons radio 38. 20. 19.findViewById(R. 6.textViewSeekBarValue). . 54. container. // le champ de saisie 59.formulaireSeekBar). } 53.findViewById(R. import java. 15. checkBox1 = (CheckBox) rootView.formulaireSwitch1). 16. 22. import java. Bundle savedInstanceState) { 30. 13. // l'activité 26.setMax(100). 42.formulaireCheckBox1). false). private DatePicker datePicker1. 24.id. 5. 44.formulaireRadioGroup).setChecked(true). // les champs de la vue affichée par le fragment 14.layout. seekBar.inflate(R. 10. 37. private EditText saisie.util. // le fragment est associé à la vue [formulaire] 31. switch1 = (Switch) rootView. } 50.List. 21. seekBar = (SeekBar) rootView. 41. public View onCreateView(LayoutInflater inflater. radioGroup = (RadioGroup) rootView. 28. 17.id. private CheckBox checkBox1. private RadioGroup radioGroup.valueOf(progress)). }). radioButton1. public void onProgressChanged(SeekBar seekBar. // on récupère l'unique activité 33. activité = (MainActivity) getActivity(). // la vue 11. private SeekBar seekBar. public void onStopTrackingTouch(SeekBar seekBar) { 49. 23. private Button buttonValider. int progress.id. RadioButton radioButton1 = (RadioButton) rootView.findViewById(R. private MainActivity activité. @Override 29. 51. saisie = (EditText) rootView. // le switch 61. // la case à cocher 36. 32. 18. private View rootView.id.3. boolean fromUser) { 55.developpez. private Switch switch1.util. } 57. 60. seekBar.id. 12.findViewById(R.id.setText(String. 27.ArrayList. 48.findViewById(R.. // le seekBar 43. rootView = inflater. private TimePicker timePicker1.. // on coche le premier bouton 40.com 122/157 .formulaireEditText1). // la date http://tahe. 7. 25. public void onStartTrackingTouch(SeekBar seekBar) { 52. private Spinner dropDownList. seekBarValue. 45. 4. 46.findViewById(R.id. 56.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 47. // on récupère les champs du formulaire 35. 62.

setDropDownViewResource(android. La ligne 64 élimine le calendrier .. progress)). 6. String radioGroupText = id == -1 ? "" : ((RadioButton) rootView. 65.setMax() permet de fixer la valeur maximale de la barre de réglage. 78. 73. Son code est le suivant : 1. } 89. On veut. 11. 86. android. } 94.formulaireEditTextMultiLignes). // la liste déroulante dropDownList = (Spinner) rootView. 72.R. // les boutons radio 8.simple_spinner_item. 9. list.findViewById(R.add(String. List<String> messages = new ArrayList<String>(). // on retourne la vue créée 87.add(String.toString(). int id = radioGroup.getCheckedRadioButtonId(). 67. // liste des messages à afficher 3. 88. 76. } 84.com 123/157 . 83. 90.developpez. // case à cocher 5. 13.id.setAdapter(dataAdapter).layout. datePicker1.setOnClickListener(new OnClickListener() { 81. list.formulaireDatePicker1). doValider(). protected void doValider() { 2. afficher la valeur de la règle dans le [TextView] de la ligne 45 .R.format("RadioGroup [checked=%s]".add("list 1"). dropDownList. radioGroupText)). lignes 80-84 : on associe la méthode [doValider] au clic sur le bouton [Valider] .simple_spinner_dropdown_item). 70. ligne 46 : on gère les événements de la barre de réglage.getText(). // le bouton 79. messages. // l'heure timePicker1 = (TimePicker) rootView. } • • • • • • • • • lignes 29-88 : dans la méthode [onCreateView] on récupère les références de tous les composants du formulaire XML [formulaire] (ligne 31) . buttonValider = (Button) rootView. ligne 44 : [SeekBar].id.layout.findViewById(R.add("list 2").id.findViewById(R. La méthode [doValider] a pour but d'afficher les valeurs saisies par l'utilisateur. 75. protected void doValider() { 92. dataAdapter. messages.format("CheckBox1 [checked=%s]".63.getProgress(). 80. 77.setCalendarViewShown(false). return rootView.formulaireDropDownList). ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(activité. 7.add(String. ligne 64 : par défaut le composant [DatePicker] affiche et une boîte de saisie de la date et un calendrier. 74. list). List<String> list = new ArrayList<String>(). // le SeekBar 12.id. lignes 75-77 : cette liste est associée à la liste déroulante . buttonValider. int progress = seekBar.. 71.formulaireTimePicker1). La valeur minimale est 0 . list. lignes 71-74 : une liste de [String] qu'on va associer à une liste déroulante .findViewById(R. 93. public void onClick(View arg0) { 82. boolean isChecked = checkBox1. 68. 85. .id. 69.isChecked(). datePicker1 = (DatePicker) rootView.findViewById(id)). // le champ de saisie multilignes multiLignes = (EditText) rootView.formulaireButtonValider). ligne 41 : la méthode [setChecked] permet de cocher un bouton radio ou une case à cocher . 64.add("list 3"). à chaque changement opéré par l'utilisateur. messages.findViewById(R. isChecked)). ligne 54 : le paramètre [progress] représente la valeur de la règle . 10. 4. http://tahe. @SuppressLint("DefaultLocale") 91. 66. }).format("SeekBar [value=%d]".

ligne 8 : la méthode [RadioGroup]. int minutes = timePicker1.setMessage(texte). La méthode [doAfficher] qui affiche la liste des valeurs saisies est la suivante : 1. messages. } • • • • • • • • • • • • • ligne 3 : les valeurs saisies vont être cumulées dans une liste de messages . // le switch boolean état = switch1. ligne 30 : la méthode [TimePicker].Builder(activité).getCurrentHour() permet d'avoir l'heure choisie avec un objet [TimePicker] . 40. 27. an)). 3. message)).com 124/157 . 34.format("Switch [value=%s]". 30. 36.getProgress()] permet d'avoir la valeur d'une barre de réglage . ligne 5 : la méthode [CheckBox]. ligne 22 : la méthode [DatePicker]. 33. 4. selectedItem)). 24.getSelectedItemPosition(). ligne 21 : la méthode [DatePicker]. mois.getCurrentMinute(). // affichage 37.findViewById(id)] permet de retrouver le bouton radio coché et d'avoir ainsi son libellé . 16. 28. messages. 2.format("Saisie simple [value=%s]". 32. ligne 29 : la méthode [TimePicker]. 19.getDayOfMonh() permet d'avoir le jour du mois choisi avec un objet [DatePicker] dans l'intervalle [1.isChecked(). %d]". 21. 9. 22. String selectedItem = String.format("Saisie multi-lignes [value=%s]".add(String. private void doAfficher(List<String> messages) { 41. ligne 34 : la méthode [Spinner]. http://tahe.format("%s\n". // l'heure int heure = timePicker1.setNeutralButton("Fermer".format("DropDownList [position=%d. // le champ de saisie String texte = String. position. %d]". int jour = datePicker1.append(String. 26. %d.getText()). 35.31] .11] . état)).getSelectedItem() permet d'avoir l'objet sélectionné dans une liste déroulante . 7.getText()).developpez. messages. 29. messages. item=%s]".valueOf(saisie.getDayOfMonth(). . 17. for (String message : messages) { texte. ligne 18 : la méthode [Switch].getYear() permet d'avoir l'année choisie avec un objet [DatePicker] .valueOf(dropDownList.getYear().. 20. } // on l'affiche new AlertDialog.format("Heure [%d. ligne 33 : la méthode [Spinner]. 42. null).getSelectedItemPosition() permet d'avoir la position de l'élément sélectionné dans une liste déroulante .add(String. jour. 6.getSelectedItem()). lignes)).getMonth() + 1.getCurrentMinute() permet d'avoir les minutes choisies avec un objet [TimePicker] ..show().add(String. 25. 38.getCurrentHour(). heure.isChecked() permet de savoir si un switch est On (true) ou Off (false) .isCkecked() permet de savoir si une case est cochée ou non . // la date int an = datePicker1. 5. } • ligne 1 : la méthode reçoit une liste de messages à afficher . // le texte multi-lignes String lignes = String.add(String. } 39.getMonth() permet d'avoir le mois choisi avec un objet [DatePicker] dans l'intervalle [0.add(String. doAfficher(messages). texte)).setTitle("Valeurs saisies"). messages. 31.format("Date [%d. ligne 9 : le code [rootView. 15. minutes)). 23. int mois = datePicker1. 18. 8. // liste déroulante int position = dropDownList. private void doAfficher(List<String> messages) { // on construit le texte à affiche StringBuilder texte = new StringBuilder().valueOf(multiLignes. messages. ligne 12 : la méthode [SeekBar].add(String. ligne 23 : la méthode [DatePicker].14.getCheckedButtonId() permet d'obtenir l'id du bouton radio qui a été coché ou -1 si aucun n'a été coché .

// les fragments 31. getCount. setMessage affiche [2].developpez. package istia. Pour concaténer des chaînes. 8. // navigation 20. public class SectionsPagerAdapter extends FragmentPagerAdapter { 29. 13. 11. setNeutralButton affiche [3]. 2. 30.com 125/157 . SectionsPagerAdapter mSectionsPagerAdapter. @Override 15.• • lignes 3-6 : un objet [StringBuilder] est construit à partir de ces messages.. le type [StringBuilder] est plus efficace que le type [String] . public void navigateToView(int i) { 21. getPageTitle 28. } 18. MyPager mViewPager. .st.. • • • 13. 22. protected void onCreate(Bundle savedInstanceState) { 16. 5. } 23.. // à redéfinir pour chaque application 26. . Ici nous n'en avons pas. 4. Fragment[] fragments = { new FormulaireFragment() }. 32.util. 6.Locale. 10. // getItem. Le 1er paramète est le libellé du bouton. Un clic sur le bouton fermera simplement la boîte de dialogue .. 24. . // le conteneur des fragments 12. public class MainActivity extends FragmentActivity { 7. 17. le second une référence sur le gestionnaire du clic sur ce bouton. 19. 14. // constructeur http://tahe. // notre gestionnaire de fragments 25. ligne 8 : une boîte de dialogue affiche le texte de la ligne 3 : 1 2 3 • ligne 8 : setTitle affiche [1]. 33. 3.android.. // le gestionnaire de fragments ou sections 9. import java..5 L'activité [MainActivity] La classe [MainActivity] reste ce qu'elle était dans les exemples précédents à quelques détails près : 1. // doit définir les méthodes suivantes 27.

toUpperCase(l). 57. 43. 35. 55. • ligne 58 : le titre du fragment [formulaire] . } 63.string. 49. 54. 48. 36.com 126/157 . 40. 53. } // doit rendre le fragment n° i avec ses éventuels arguments @Override public Fragment getItem(int position) { // on rend le fragment return fragments[position]. 39. 62. 42. 60.formulaire_titre).getDefault(). 44. } public SectionsPagerAdapter(FragmentManager fm) { super(fm). 46. 59. 41.developpez. 51. http://tahe. 58. 64. 47. 38. 50. } Les modifications sont : • ligne 31 : on instancie le fragment du formulaire . } // rend le nombre de fragments à gérer @Override public int getCount() { // 1 fragments return 1. switch (position) { case 0: return getString(R. } // rend le titre du fragment n° position @Override public CharSequence getPageTitle(int position) { Locale l = Locale. 61. 37. 45. 56. 52.34. } return null.

developpez.com 127/157 . une colonne de gauche qui pourrait contenir des liens .14 Exemple-13 : utilisation d'un patron de vues L'application [exemple-13-template] est obtenue par recopie du projet [exemple-06]. en [4]. un entête . en [3]. 1 2 4 3 Le code XML de la vue [activity_main] est le suivant : http://tahe. un bas de page . un contenu. Ceci est obtenu en modifiant la vue de base [activity_main. Les deux projets sont identiques si ce n'est qu'on change l'apparence des vues : 1 2 4 3 Chacune des deux vues est structurée de la même façon : • • • • en [1]. en [2].xml] de l'activité .

<TextView 16. android:layout_width="100dp" 35. 48. android:layout_height="100dp" 12. android:layout_width="match_parent" 62. android:id="@+id/bottom" 61. 8. android:background="@color/lavenderblushh2" > 14. android:layout_height="100dp" 63. </LinearLayout> 25. android:textAppearance="?android:attr/textAppearanceLarge" 45. android:layout_height="wrap_content" 19. android:layout_gravity="center" 20. android:layout_weight="0. android:id="@+id/txt_left" 40. android:textAppearance="?android:attr/textAppearanceLarge" 23. android:layout_height="fill_parent" 42.com/apk/res/android" 50. <TextView 39. android:text="@string/txt_header" 22.android. 26.1.MyPager 49. android:textColor="@color/red" /> 24.android. android:layout_width="match_parent" 53. android:layout_weight="0. <LinearLayout 27. <LinearLayout 9. <istia. 59. android:orientation="horizontal" > 31. 38. android:textColor="@color/red" /> 46.8" 30. xmlns:android="http://schemas.1" 13.st.android. android:layout_width="match_parent" 11. android:layout_marginLeft="20dp" 55. android:gravity="center_horizontal" 21. android:orientation="vertical" > 7. tools:context=". <LinearLayout 33.MainActivity" /> 57. android:layout_width="match_parent" 28. 15. <LinearLayout 60. android:layout_height="fill_parent" 29. android:id="@+id/textViewHeader" 17. </LinearLayout> 58. android:layout_width="match_parent" 4. android:gravity="center_vertical|center_horizontal" 43. xmlns:tools="http://schemas.com/apk/res/android" 2. </LinearLayout> 47. <LinearLayout xmlns:android="http://schemas. android:id="@+id/left" 34.com/tools" 51. android:id="@+id/header" 10.com/tools" 3. android:layout_width="match_parent" 18.com 128/157 . android:layout_height="match_parent" 54.1" http://tahe. android:text="@string/txt_left" 44. android:layout_width="fill_parent" 41. android:layout_weight="0. android:background="@color/lightcyan2" > 37.android.android. android:layout_height="match_parent" 5. android:layout_height="match_parent" 36. android:id="@+id/pager" 52. android:background="@color/floral_white" 56. 32. android:gravity="center" 6.developpez. xmlns:tools="http://schemas.

5.0" encoding="utf-8"?> 2.developpez. <resources> 3. 8. bande gauche. </LinearLayout> 75. android:id="@+id/textViewBottom" 68. 4.64.xml] : Le fichier [colors. 76. android:textColor="@color/red" /> 74. // il est associé à notre gestionnaire de fragments mViewPager. ligne 5 : le composant d'id [pager] est le conteneur qui recevra les vues gérées par le [ViewPager]. android:layout_height="fill_parent" 70. le contenu [4] est obtenu avec les lignes 48-57. 2. le bas de page [3] est obtenu avec les lignes 59-74 . android:background="@color/wheat1" > 65. android:textAppearance="?android:attr/textAppearanceLarge" 73. <?xml version="1. 6. • // instanciation de notre gestionnaire de fragments mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()). <color name="floral_white">#FFFAF0</color> 8. 3. <color name="red">#FF0000</color> 5.setSwipeEnabled(false). <color name="wheat">#FFEFD5</color> 7. Le reste (entête. <TextView 67.xml] et [res / values / strings.com 129/157 . // on récupère la référence du conteneur de fragments mViewPager = (MyPager) findViewById(R. 9.id. On notera le nom [pager] de ce conteneur (ligne 51). Maintenant rappelons le code dans l'activité [MainActivity] qui affiche une vue : 1. 7. <color name="lavenderblushh2">#EEE0E5</color> http://tahe.setAdapter(mSectionsPagerAdapter). <color name="blue">#0000FF</color> 6. bas de page) est conservé. la bande gauche [2] est obtenue avec les lignes 32-46 . android:text="@string/txt_bottom" 72. On a là une méthode analogue à celle des templates des facelets de JSF2 (Java Server Faces). android:layout_width="fill_parent" 69. android:gravity="center_vertical|center_horizontal" 71.pager).xml] est le suivant : 1. 4. </LinearLayout> • • • • l'entête [1] est obtenu avec les lignes 8-24 . 66. // on inhibe le swipe mViewPager. La vue XML [activity_main] utilise des informations trouvées dans les fichiers [res / values / colors.

<string name="txt_bottom">Bottom</string> 16. <color name="lightcyan2">#D1EEEE</color> 10. <?xml version="1.developpez. <string name="txt_header">Header</string> 14.0" encoding="utf-8"?> 2. 4. <resources> 3. <string name="app_name">exemple-13</string> 5. </resources> et le fichier [strings. <string name="vue1_titre">Vue n° 1</string> 7. <string name="txt_left">Left</string> 15. 17. <string name="btn_vue2">Vue n° 2</string> 10. <string name="textView_bonjour">"Bonjour "</string> 13.9.xml] le suivant : 1. <string name="textView_nom">Quel est votre nom :</string> 8. </resources> http://tahe. <string name="btn_vue1">Vue n° 1</string> 12. <string name="action_settings">Settings</string> 6. <string name="vue2_titre">Vue n° 2</string> 11. <string name="btn_Valider">Validez</string> 9. 12. <color name="wheat1">#FFE7BA</color> 11.com 130/157 .

<name>exemple-14-listview</name> http://tahe. <packaging>apk</packaging> 6. 15. • un [CheckBox] . <version>0.developpez.0.15 Exemple-14 : le composant [ListView] Le composant [ListView] permet de répéter une vue particulière pour chaque élément d'une liste. <modelVersion>4.xml] est adapté au nouveau projet : 1.1-SNAPSHOT</version> 5. <artifactId>exemple-14-listview</artifactId> 4.0</modelVersion> 2.com 131/157 . • un [TextView] cliquable . <groupId>exemples</groupId> 3. Nous allons créer le [ListView] suivant : Chaque vue de la liste a trois composants : • un [TextView] d'information . d'une simple chaîne de caractères à une vue permettant de saisir des informations pour chaque élément de la liste. • la classe [ListAdapter] [2] qui est le code Java associé à la vue XML [list_data] . Le fichier [pom.1 Le projet Eclipse Le projet [exemple-14-listview] est obtenue par recopie du projet précédent [exemple-13-template] : 2 1 Les éléments nouveaux de ce projet sont : • la vue XML [list_data] [1] qui est la vue répétée par le composant [ListView] .0. La vue répétée peut être d'une complexité quelconque.

android:layout_marginTop="50dp" http://tahe. android:layout_width="wrap_content" 20. android:text="@string/vue1_titre" 15. android:layout_width="match_parent" 4. android:layout_height="wrap_content" 21. <Button 18. android:layout_height="wrap_content" 10.android. android:layout_alignLeft="@+id/listView1" 22. android:id="@+id/button_vue2" 19. android:layout_height="match_parent" > 5. android:textSize="50sp" /> 16. <RelativeLayout xmlns:android="http://schemas.0" encoding="utf-8"?> 2. <TextView 7.xml] affiche la zone [1] ci-dessus. android:layout_below="@+id/listView1" 23. android:layout_alignParentTop="true" 12.2 La vue [Vue1] initiale 1 2 3 4 La vue XML [vue1. android:layout_alignParentLeft="true" 11. 6. android:layout_width="wrap_content" 9. android:layout_marginTop="26dp" 14.developpez. <?xml version="1. android:id="@+id/textView_titre" 8.15. 17. Son code est le suivant : 1.com 132/157 .com/apk/res/android" 3. android:layout_marginLeft="88dp" 13.

android:layout_marginTop="20dp" 14. android:layout_alignParentLeft="true" 31.3 lignes 6-15 : le composant [TextView] [2] .developpez. <ListView 27. android:id="@+id/listView1" 28. 26. android:layout_height="200dp" 30. lignes 26-34 : le composant [ListView] [3] . <RelativeLayout xmlns:android="http://schemas. android:layout_height="wrap_content" 12. android:layout_alignBaseline="@+id/txt_Libellé" 30. android:id="@+id/checkBox1" 18. android:layout_marginLeft="68dp" 32. android:layout_toRightOf="@+id/txt_Libellé" 23.com/apk/res/android" 3. La vue répétée par le [ListView] 1 2 3 La vue répétée par le [ListView] est la vue [list_data] suivante : 1. <TextView 26. android:text="@string/txt_dummy" /> 15. android:layout_height="wrap_content" 29. android:id="@+id/textViewRetirer" 27. android:text="@string/btn_vue2" /> 25. android:layout_width="600dp" 29. android:id="@+id/RelativeLayout1" 4. 8. </ListView> 35. android:layout_toRightOf="@+id/checkBox1" 33. android:layout_below="@+id/textView_titre" 32.android. android:text="@string/txt_dummy" /> 24.0" encoding="utf-8"?> 2. android:layout_height="wrap_content" 20. android:layout_width="match_parent" 5. android:text="@string/txt_retirer" http://tahe. </RelativeLayout> • • • 15.24. 36. android:layout_marginLeft="20dp" 13. android:layout_alignBottom="@+id/txt_Libellé" 21. android:id="@+id/txt_Libellé" 10. 16. android:layout_width="100dp" 11. android:layout_width="wrap_content" 19. <CheckBox 17. android:background="@color/wheat" > 7. android:layout_marginLeft="30dp" 33. android:layout_marginLeft="37dp" 22. lignes 17-24 : le composant [Button] [4] . android:layout_height="match_parent" 6. android:layout_marginTop="50dp" > 34.com 133/157 . android:layout_alignBottom="@+id/txt_Libellé" 31. 25. <TextView 9. android:layout_width="wrap_content" 28. <?xml version="1.

private Button btnVue2. listView = (ListView) rootView. http://tahe.4 lignes 8-14 : le composant [TextView] [1] . private ListView listView.id. adapter = new ListAdapter(activité. 6. btnVue2 = (Button) rootView. listView. // un fragment est une vue affichée par un conteneur de fragments 7. </RelativeLayout> • • • 15.com 134/157 ..button_vue2). android:textSize="20sp" /> 36. activité. // on récupère les composants de la vue 22. 36. public void onClick(View arg0) { 28.List. // l'activité 13.android. 34. 14.util. container.findViewById(R. R. 2. private MainActivity activité. 23. } 31.34. return rootView. private ListAdapter adapter.setAdapter(adapter). 21. package istia.layout.list_data. android:textColor="@color/blue" 35. import java. .vue1. navigateToView2(). // gestionnaire d'évts 25. 37. 24. // l'adaptateur de liste 15. this). // les champs de la vue affichée par le fragment 10.. 16. Le fragment [Vue1Fragment] Le fragment [Vue1Fragment] gère la vue XML [vue1]. 17.developpez. ViewGroup container.st. @Override 18. lignes 25-35 : le composant [TextView] [3] . Son code est le suivant : 1. activité = (MainActivity) getActivity(). 3. public View onCreateView(LayoutInflater inflater. 30. // on récupère l'unique activité 33. 11. 5.id. // on associe des données au [ListView] 35.inflate(R. public class Vue1Fragment extends Fragment { 8. btnVue2. 9. }). // on passe à la vue n° 2 29. // on retourne la vue créée 38. 12.setOnClickListener(new OnClickListener() { 27.findViewById(R. false). 4. 32. Bundle savedInstanceState) { 19. lignes 16-23 : le composant [CheckBox] [2] .listView1). 37. // le fragment est associé à la vue [vue1] 20.getListe(). // bouton [Vue2] 26.layout. View rootView = inflater.

. Celle-ci sera utilisée pour faire gérer le clic sur un lien [Retirer] du [ListView] par la méthode [doRetirer] de la ligne 46 .android. 2. } 49.developpez. 4. } 40. // on navigue vers la vue 2 43. } // une liste de données private List<Data> liste. 9. ligne 36 : on associe à ce [ListView] une source de données de type [ListAdapter] (ligne 35). 5.. • une référence sur le fragment. // données 6. private boolean isChecked. false)). . 3. 2. i < 20. 1.add(new Data("Texte n° " + i. 6. La source de données est définie dans [MainActivity] . ligne 35 : nous passons diverses informations au constructeur de [ListAdapter] : • une référence sur l'activité courante. public void doRetirer(int position) { 47. // constructeur http://tahe. 5. 48. • une source de données pour alimenter la liste. package istia. public class Data { 4.com 135/157 . • l'id de la vue qui sera instanciée pour chaque élément de la liste. Nous allons construire cette classe.navigateToView(1). 7. for (int i = 0. 8. 11. 10. ligne 22 : on récupère une référence sur le composant [ListView] de [vue1] . } 45. // constructeur public MainActivity() { // on crée une liste de données liste = new ArrayList<Data>(). private void navigateToView2() { 42. 9. } Nous ne commentons que ce qui est nouveau : • • • • ligne 20 : la vue XML [vue1] est associée au fragment . 7. 41. Elle dérive de la classe [ArrayAdapter] que nous avons déjà eu l'occasion d'utiliser pour associer des données à un [ListView] . 3.st. activité. 46. 8. i++) { liste. private String texte. } Le constructeur de la classe [MainActivity] crée 20 données du type [Data] suivant : 1. 44.39.

19. 14. } public boolean isChecked() { return isChecked. } public void setChecked(boolean isChecked) { this.com 136/157 .texte = texte. this. 17. 15. 21. 18.isChecked = isCkecked. 11. 31. 28. . } public void setTexte(String texte) { this. 13.5 public Data(String texte. 26. 3. // les données de la liste 12. public class ListAdapter extends ArrayAdapter<Data> { 6. private Vue1Fragment fragment. Son code est le suivant : 1. 4.st.10. ligne 7 : le booléen qui va servir à cocher ou non le [checkBox] de [list_data] . 27.developpez. private Context context.isChecked = isChecked. 11. } // getters et setters public String getTexte() { return texte. 12. import java. 32. package istia.. 5. • gère l'affichage des différents éléments du [ListView] . 30.texte = texte. // le contexte d'exécution 8. L'adaptateur [ListAdapter] du [ListView] La classe [ListAdapter] • configure la source de données du [ListView] . 16.android. } ligne 6 : le texte qui va alimenter le premier [TextView] de [list_data] . 22.List.util. 24. private int layoutResourceId. • gère les événements de ces éléments . 9. 25. // l'id du layout d'affichage d'une ligne de la liste 10. 29. boolean isCkecked) { this. // le fragment qui affiche le [ListView] 14. 7. 13. 20. 23. } • • 15. private List<Data> data. 15. // l'adapteur http://tahe. 2..

setText(data. 8.textViewRetirer). View convertView. // constructeur public ListAdapter(Context context.setChecked(data.context = context.get(position).. // on mémorise les infos 22.getLayoutInflater(). // la case à cocher 9. 15. txtRetirer.fragment = fragment. data.findViewById(R. 23. textView. 17. ViewGroup parent) { 30.id. }). @Override 2. List<Data> data. boolean isChecked) { 23. // on gère le clic sur la case à cocher 20. public void onClick(View v) { 16. 19. // le texte 6. 18. Le résultat [View] rendu est une référence sur la vue créée. 19. TextView textView = (TextView) row.findViewById(R. 24. 7. On récupère les références des composants de la vue qu'on vient d'instancier . this. checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { 21. ligne 19 : le constructeur . lignes 22-25 : on mémorise les informations du constructeur . 28..get(position).inflate(layoutResourceId. checkBox. 17. // le lien [Retirer] 12. } 32.setOnClickListener(new OnClickListener() { 14. this. data). fragment. 28. 21. 31. final ListAdapter adapter = this. 26.id. CheckBox checkBox = (CheckBox) row. // on rend la ligne 27. } 18. ViewGroup parent) { 3. Nous n'allons utiliser que le premier .getTexte()). layoutResourceId.get(position). } • • • • • ligne 5 : la classe [ListAdapter] étend la classe [ArrayAdapter] . public View getView(final int position. }). @Override 29.isChecked()). ligne 20 : ne pas oublier d'appeler le constructeur de la classe parent [ArrayAdapter] avec les trois premiers paramètres . . http://tahe. parent. } • • • la méthode reçoit trois paramètres. int layoutResourceId.findViewById(R. ligne 6 : on récupère la référence du [TextView] n° 1 . 24. 13. 26. 10. } 25.doRetirer(position). 11.txt_Libellé). 25. this. public View getView(final int position. 22.checkBox1). View convertView. C'est la vue [list_data] dont l'id a été passé comme deuxième paramètre au constructeur.com 137/157 .layoutResourceId = layoutResourceId.id. Ensuite on procède comme d'habitude. return row.data = data. Le code de la méthode [getView] est le suivant : 1. ligne 29 : la méthode [getView] va être appelée de façon répétée par le [ListView] pour générer la vue de l'élément n° [position]. Vue1Fragment fragment) { 20. false).setChecked(isChecked).developpez. super(context. ligne 4 : on crée la vue de l'élément n° [position]. View row = ((Activity) context). 5. public void onCheckedChanged(CompoundButton buttonView.16. TextView txtRetirer = (TextView) row. this. // on crée la ligne 4. } 27.

13. Avec les lignes ci-dessus. • la ligne 15 ci-dessus le réinitialise alors totalement et le [ListView] affiche alors les lignes 0-3 de la liste de données . Le [ListView] est une liste qui n'affiche qu'une partie de ces éléments.setAdapter(adapter). Sans elle. lignes 5-13.doRetirer qui va gérer ce clic.getChildAt(0).com 138/157 . Il faut donc que celle-ci mémorise l'état de la case à cocher au fil du temps . ligne 9 : on récupère la référence du [CheckBox] n° 2 .com/questions/3014089/maintain-save-restore-scrollposition-when-returning-to-a-listview] 8. lignes 13-18 : on gère le clic sur le lien [Retirer] .getListe(). lignes 20-25 : on gère le clic sur la case à cocher. Sans cela.setSelectionFromTop(firstPosition. // on note la position du scroll pour y revenir // lire // [http://stackoverflow. la méthode [getView] de la ligne 2 ci-dessus est appelée pour la position n° i. Il a une vue d'ensemble que n'a pas la classe [ListAdapter]. listView. • on supprime la ligne 16. ligne 3 : on récupère la liste de données . int firstPosition = listView.getFirstVisiblePosition(). La référence du fragment [Vue1Fragment] avait été passée comme quatrième paramètre au constructeur de la classe . Ainsi un élément de la liste est-il parfois caché. // mesure la hauteur de la partie éventuellement cachée 12. public void doRetirer(int position) { // on enlève l'élément n° [position] dans la liste List<Data> liste = activité. 5.getTop(). // offset Y de cet élément par rapport au haut du ListView 11. visuellement rien ne change. // on réassocie des données au [ListView] 15.7 ligne 1 : on reçoit la position dans le [ListView] du lien [Retirer] qui a été cliqué . 4. la suppression se fait et le [ListView] reste positionné sur la ligne qui suit la ligne supprimée. L'action faite sur elle est répercutée sur la donnée qu'elle affiche. top). Lorsque l'élément n° i doit être affiché. listView.• • • • • • • ligne 7 : on lui assigne un texte provenant de la source de données qui a été passée comme troisième paramètre au constructeur . liste.developpez. parfois affiché. 7. // on se positionne au bon endroit du ListView 17. ligne 15 : on réassocie les nouvelles données au [ListView]. } • • • • • 15. View v = listView. // position du 1er élément visible complètement ou non 9. Il paraît en effet plus logique de faire gérer cet événement par le fragment qui affiche le [ListView]. ligne 10 : on le coche ou non avec une valeur provenant de la source de données du [ListView] . int top = (v == null) ? 0 : v. ligne 12 : on récupère la référence du [TextView] n° 3 .remove(position). 17 : une gymnastique assez complexe. 6. La ligne 10 va recalculer l'état de la case à cocher à partir de la donnée à laquelle elle est liée. Ceci pour la raison suivante. ligne 4 : on retire l'élément de n° [position] . il se passe la chose suivante : • le [ListView] affiche les lignes 15-18 de la liste de données. 2. 19. 14. 3. ligne 16 : c'est la méthode [Vue1Fragment]. 18. 16. La vue XML [Vue2] http://tahe. 15. 10.6 Retirer un élément de la liste Le clic sur le lien [Retirer] est gérée dans le fragment [Vue1Fragment] par la méthode [doRetirer] suivante : 1.

</RelativeLayout> • • • 15. android:layout_height="wrap_content" 10. 25. android:layout_marginTop="25dp" 23. android:layout_marginTop="50dp" 31.developpez. android:layout_width="wrap_content" 28. android:layout_width="wrap_content" 20. lignes 17-23 : le composant [Button] n° 3 . 17. android:id="@+id/button_vue1" 19. android:layout_marginTop="26dp" 14.com/apk/res/android" 3. android:id="@+id/textView_titre" 8.com 139/157 . Le fragment [Vue2Fragment] http://tahe. android:layout_below="@+id/textViewResultats" 22. android:layout_height="wrap_content" 29. 6. android:text="@string/vue2_titre" 15. android:layout_height="wrap_content" 21. android:layout_width="match_parent" 4. android:id="@+id/textViewResultats" 27. <Button 18.1 2 3 Le code XML de la vue est le suivant : 1. android:text="" /> 32.8 lignes 6-15 : le composant [TextView] n° 1 . lignes 25-31 : le composant [TextView] n° 2 . android:layout_width="wrap_content" 9. <TextView 7. android:layout_below="@+id/textView_titre" 30. android:layout_marginLeft="88dp" 13.0" encoding="utf-8"?> 2. <TextView 26. android:layout_alignParentLeft="true" 11. android:layout_alignParentTop="true" 12. android:text="@string/btn_vue1" /> 24.android. <RelativeLayout xmlns:android="http://schemas. <?xml version="1. 33. android:layout_height="match_parent" > 5. android:textSize="50sp" /> 16.

false). super. . // un fragment est une vue affichée par un conteneur de fragments 7. private Button btnVue1.com 140/157 .on affiche les éléments de la liste qui ont été 41. StringBuilder texte = new StringBuilder("Eléments sélectionnés ["). btnVue1 = (Button) rootView. 46. 22. // le fragment est associé à la vue [vue2] 18.findViewById(R. 36. 9. txtRésultats = (TextView) rootView. btnVue1. @Override 16. private MainActivity activité. } 35. // on retourne la vue créée 33. 32. for (Data data : activité. ViewGroup container. } 29.id. 30. 43. // gestionnaire d'évts 23. } 48. http://tahe. return rootView. 6.os. texte. Bundle savedInstanceState) { 17. 4. // on récupère l'unique activité 31.st. View rootView = inflater.button_vue1).layout. 3. private TextView txtRésultats. 39. 15. data. activité = (MainActivity) getActivity().Bundle. if (data.append(String. 5. public void onClick(View arg0) { 26.id. @Override 37. // bouton [Vue1] 24. import android. // sélectionnés 42.developpez. }).format("(%s)". navigateToView1(). container.textViewResultats). Son code est le suivant : 1.inflate(R. 14.vue2. 12. // la vue est visible . 11. public void setMenuVisibility(final boolean visible) { 38.setOnClickListener(new OnClickListener() { 25. if (visible) { 40. // les champs de la vue 10. 21..findViewById(R. 2.getListe()) { 44. public class Vue2Fragment extends Fragment { 8. texte.getTexte())). package istia. 19.append("]").setMenuVisibility(visible). // on passe à la vue n° 1 27. public View onCreateView(LayoutInflater inflater.. 28.android. // l'activité 13.isChecked()) { 45.1 2 3 Le fragment [Vue2Fragment] gère la vue XML [vue2]. // on récupère les composants de la vue 20. } 47. 34.

// on navigue vers la vue 1 55. this.49. private String texte. boolean isCkecked) { 11. http://tahe.st.st. } 57. activité. . Exécution Créez une configuration d'exécution pour ce projet et exécutez-la. 56. 2. ligne 49 : le [TextView] affiche le texte calculé . 53. // constructeur 10. le [ListView] doit afficher des données qu'on peut sélectionner en cochant une case sans que pour autant l'élément de la source de données ait un champ booléen correspondant à cette case. 8. } Le code important est dans la méthode [setMenuVisibility] de la ligne 37. 16. } Ligne 7.com 141/157 . 7.. • • • • • 15. // constructeur 9. this.texte = texte. 9. private boolean isChecked. On peut alors procéder de la façon suivante : La classe [Data] devient la suivante : 1.developpez. 5. } 52.navigateToView(0). txtRésultats. } 51. } 14. 13. public class Data { 4. package istia. package istia. 5. lignes 43-46 : on parcourt la liste des données affichée par le [ListView].9 ligne 38 : on appelle la méthode de la classe parent. public class Data { 4. 58. 15.android. 7.setText(texte). Elle est stockée dans l'activité . 3. private void navigateToView1() { 54. 8. 2.10 Amélioration Dans l'exemple précédent nous avons utilisé une source de données List<Data> où la classe [Data] était la suivante : 1. this. 3.. private String texte. public Data(String texte. 15. Souvent. alors on calcule le texte à afficher dans le [TextView] n° 2 . ligne 39 : si la vue va devenir visible. 50.isChecked = isCkecked. 12.android. // données 6. on ajoute le libellé associé dans un type [StringBuilder] . on avait utilisé un booléen pour gérer la case à cocher des éléments du [ListView]. Elle est exécutée dès que la vue affichée par le fragment va devenir visible ou cachée à l'utilisateur. ligne 45 : si la donnée n° i a été cochée. public Data(String texte) { 10. // données 6.texte = texte. Obligatoire .

// getters et setters 14. 11. 4. 10.. 8.android. // élément coché 6. false)). package istia. 8. 15. } Le projet de cette version vous est fourni sous le nom [exemple-15-listview]. le type [Data] par le type [CheckedData]. . i < 20. // constructeur public MainActivity() { // on crée une liste de données liste = new ArrayList<CheckedData>(). } // une liste de données à cocher private List<CheckedData> liste. this. 3. 5. } 12.. 16. // local 13.st.com 142/157 . public CheckedData(String text. 9. 7. 13.add(new CheckedData("Texte n° " + i. 3.. Par exemple dans [MainActivity] : 1. public class CheckedData extends Data { 4. i++) { liste. 14. 5. 18.. super(text). } 15. 7. 2. 12. // constructeur 9.isChecked = isChecked. } Il suffit ensuite de remplacer partout dans le code. private boolean isChecked. // getters et setters 17. 2. http://tahe. . 6. // parent 11.11. for (int i = 0.developpez. } On crée une classe [CheckedData] dérivée de la précédente : 1. boolean isChecked) { 10.

nous voyons les options de menu de la vue n° 1. <menu xmlns:android="http://schemas. android:title="@string/actionValider"/> 11. android:showAsAction="ifRoom" 16. </item> 13. 3. <menu> 18. En [2] et [3]. android:showAsAction="ifRoom" 6. <item 19. android:id="@+id/menuActions" 5. <item 9. <menu> 8. android:id="@+id/navigationVue1" 20. </menu> 12.16 Exemple-16 : utiliser un menu Nous reprenons le projet [exemple-13-template] que nous dupliquons dans le projet [exemple-16-menu] [1] : 2 3 1 Nous supprimons les boutons des vues 1 et 2 pour les remplacer par des options de menu.com 143/157 . android:title="@string/menuNavigation"> 17. <item 14. android:id="@+id/navigationVue2" 23. android:id="@+id/actionValider" 10. <item 22.android. android:title="@string/navigationVue2"/> 24. android:title="@string/navigationVue1"/> 21.developpez. Nous le modifions de la façon suivante : 1.com/apk/res/android" > 2. android:title="@string/menuActions"> 7. android:id="@+id/menuNavigation" 15. </menu> http://tahe.1 La définition XML du menu Le fichier [res / menu / main] est présent par défaut et contient la définition d'un menu vide. 16. <item 4.

@Override 2. // le menu private boolean menuDone = false. Pour indiquer cela. le menu est géré par les méthodes [onCreateOptionsMenu] et [onOptionsItemSelected]. on demande les références des différents fragments à notre gestionnaire de pages qui étend la classe [FragmentPagerAdapter]. 27. [ifRoom] indique que l'élément doit être placé dans la barre d'actions s'il y a de la place pour lui . } 6.getItem(i).2 La gestion du menu dans l'activité [MainActivity] Dans l'activité principale. 5. 7. i < mSectionsPagerAdapter.developpez. 16. </item> 26. • android:showsAsAction : indique si l'élément de menu peut être placé dans la barre d'actions de l'activité. for (int i = 0.getCount(). public boolean onCreateOptionsMenu(Menu menu) { 3. 4. Ci-dessus.setHasOptionsMenu(true). </menu> Ce menu correspond à la hiérarchie suivante (onglet Layout lorsque le fichier XML est visualisé) : Les éléments du menu sont définis par les informations suivantes : • android:id : l'identifiant de l'élément .setHasOptionsMenu().3 La gestion du menu dans le fragment [Vue1Fragment] Dans les fragments. return true. } Les fragments vont gérer leur propre menu. 2. La méthode [onCreateOptionsMenu] est la suivante : 1. 5. la gestion du menu se fait de la façon suivante : 1. MenuInflater inflater) { http://tahe. mSectionsPagerAdapter. on doit appeler la méthode [Fragment]. • android:title : le libellé de l'élément . @Override public void onCreateOptionsMenu(Menu menu.com 144/157 . 3.25. i++) { 4. 16.

inflate(R. menuDone=false. 9. } menuDone = true.setVisible(false). case R.setVisible(false).6. c'est la ligne 10 qui crée le menu à partir de sa définition XML . // on cache des options 9.setVisible(false).getItem(1).com 145/157 . 11.getItemId()) { 5. les fragments se contentant de cacher les options qui ne les concernent pas et d'afficher celles qui les concernent. On a vu que c'était l'activité qui l'avait fait. MenuInflater inflater) { 3. // on le remplit 7. 8. 12. 11. la méthode de la ligne 5 n'est appelée que si la méthode [Fragment].main. @Override 2. lignes 9-11 : dans le cas de l'élément [Actions / Valider].navigationVue2: 6. 11.developpez. break. on exécute la méthode [doValider] . 8.setHasOptionsMenu() a été appelée auparavant. La gestion du menu dans le fragment [Vue2Fragment] On retrouve un code similaire dans le fragment de la vue n° 2 : 1. // on gère l'action du menu 4.main. // on cache l'option [Navigation / Vue 1] menu. menu. Aussi utilise-t-on la variable [ menuDone] de la ligne 2 pour éviter ce phènomène . ligne 4 : pour reconnaître l'élément cliqué.clear(). menu. Note : la solution utilisée ici n'est pas optimale. 10.menu. // on nettoie le menu 5.id. ligne 8 : le menu est vidé de tous ses éléments avant d'être régénéré .actionValider: 10. 15. case R. 13. break. 14. inflater. lignes 5-8 : dans le cas de l'élément [Navigation / Vue 2]. La méthode [onOptionsItemSelected] gère les clics sur les options du menu : 1. on navigue vers la vue n° 2 (ligne 7). 7. On ne garde que les options [Actions / Valider] et [Navigation / Vue 2] . } 13. menu.getItem(1). 6. @Override 2. switch (item. if (!menuDone) { 4. menu). 14.inflate(R. 12. return true. on utilise son id . doValider().getItem(0). 9. 8. On constate aux tests que la méthode [onCreateOptionsMenu] est appelée plusieurs fois et que les options se cumulent alors dans le menu. ligne 12 : l'option [Vue 1] du sous-menu [Navigation] est cachée.getItem(1). on met le booléen [menuDone] à faux pour régénérer le menu lorsqu'on reviendra plus tard sur la vue n° 1 . } • • • • if (!menuDone) { // on nettoie le menu menu. menu). public boolean onOptionsItemSelected(MenuItem item) { 3. } http://tahe. navigateToView2().getItem(0). Par ailleurs.4 ligne 2 : on reçoit la référence de l'élément de menu qui a été cliqué . // on le remplit inflater.getSubMenu(). 7. } • • • • 16.clear().menu. 10.id. public void onCreateOptionsMenu(Menu menu.getSubMenu(). Le menu devrait être généré une unique fois par l'activité.

} @Override public boolean onOptionsItemSelected(MenuItem item) { // on gère l'action du menu switch (item. } return true.getItemId()) { case R. 25. 16. on appelle la méthode [navigateToView1] .developpez. } ligne 9 : on cache l'option [Actions] .com 146/157 .id. 20. 13.navigationVue1: menuDone=false. 14. navigateToView1(). 19.12. • • • 16. 18. 15. 24. http://tahe. lignes 19-22 : lors du clic sur l'option [Navigation / Vue1]. ligne 10 : on cache l'option [Navigation / Vue 2] . break. 17. 22. 23. 21. Exécution Créez un contexte d'exécution pour ce projet et exécutez-le.5 menuDone = true.

2 Installation et test du serveur REST Le binaire Java du serveur REST est fourni : Pour lancer le serveur REST. il faut construire le client Android [2]. placez vous dans le dossier du jar .1 Introduction Pour appliquer ce qui a été vu précédemment. procédez de la façon suivante : • • ouvrez une fenêtre DOS . 17. http://tahe.com/fichiers-archive/android-exemples.developpez. L'application aura une architecture client / serveur : • • le serveur [1] est founi .com 147/157 .ftpdeveloppez.zip]. Pour faire ce travail un certain nombre d'éléments sont fournis dans le fichier des exemples [http://tahe.17 Exercice d'application 17. nous proposons maintenant un travail consistant à écrire un client Android pour tablette permettant de simuler des calculs de feuille de salaire des employés d'une association.

.396 INFO 4064 --. :: Spring Boot :: (v0. 2014-01-08 14:47:01.exe].• tapez la commande : java -jar server-pam. autowireCandidate=true. factoryBeanName=org. defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguratio n.M6) 8.u45\bin\java -jar server-pam.annotation.s. destroyMethodName=(inferred).transaction. =========|_|==============|___/=/_/_/_/ 7.embedded. ' |____| .jpa.jar Cela suppose que le binaire [java.springframework.springframework.ProxyTransactionManagementConfiguration$Enhanc erByCGLIB$$a8a9b58a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 13.[ main] boot. factoryBeanName=org. ____ _ __ _ _ 2.internalTransactionAdvisor' of type [class org. tapez le chemin complet de [java.boot. factoryMethodName=transactionManager. abstract=false.HibernateJpaAutoConfigurat ion.Application : Starting Application on Gportpers3 with PID 4064 (C:\Users\Serge TahÚ\Desktop\part3\server-pam.annotation.transaction.jdbc.[ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.[ main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org. autowireCandidate=true. | / / / / 6.027 INFO 4064 --. primary=false.boot.ProxyTransactionManagementConfiguration' of type [class org.exe] est dans le PATH de votre machine.annotation.DataSourceTransactionManagerA utoConfiguration.[ main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework. scope=. dependencyCheck=0. 2014-01-08 14:47:02.[ main] o.7. destroyMethodName=(inferred).AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 14.context.480 INFO 4064 --.springframework.jar started by ST) 10.AnnotationConfigEmbeddedWebApplicationContext@7 d29af05: startup date [Wed Jan 08 14:47:01 CET 2014]. 2014-01-08 14:47:02.jar Une fenêtre DOS va s'ouvrir et afficher des logs : 1.boot. scope=.b.0.class]] 12.config.interceptor.DefaultListableBeanFactory : Overriding bean definition for bean 'transactionManager': replacing [Root bean: class [null].orm. dependencyCheck=0. initMethodName=null.5.springframework.springframework.springframework. 2014-01-08 14:47:02.transaction.autoconfigure.class]] with [Root bean: class [null]. par exemple : D:\Programs\devjava\java\jdk1.transaction.[ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.interceptor. abstract=false. lazyInit=false. primary=false.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) http://tahe. lazyInit=false. root of context hierarchy 11.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 15.[ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.439 INFO 4064 --. \/ ___)| |_)| | | | | || (_| | ) ) ) ) 5.s. Si ce n'est pas le cas. 2014-01-08 14:47:01.transaction.f.__|_| |_|_| |_\__. defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration. autowireMode=3.com 148/157 . /\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 3. 9.springframework.462 INFO 4064 --.157 INFO 4064 --.transaction. 2014-01-08 14:47:02. autowireMode=3.autoconfigure. factoryMethodName=transactionManager. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 4.developpez.192 INFO 4064 --. initMethodName=null. 2014-01-08 14:47:02.

s.hibernate.840 INFO 4064 --. [/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 21. 2014-01-08 14:47:04.s.s.h. 2014-01-08 14:47:03.ejb.578 INFO 4064 --.resource.dialect.431 INFO 4064 --..[ost-startStop-1] o.web.[ost-startStop-1] org.hibernate.[Tomcat].c.[ost-startStop-1] org.s.lang.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ContextLoader : Root WebApplicationContext: initialization completed in 2122 ms 20.] 31.servlet.hibernate.hibernate.e.1.ast.params=[].developpez.web.cfg.787 INFO 4064 --.[ost-startStop-1] o..catalina.i.headers=[].147 INFO 4064 --.ASTQueryTranslatorFactory : HHH000397: Using ASTQueryTranslatorFactory 35.hibernate.369 INFO 4064 --.methods=[GET].16.params=[].web.String rest.web.[ main] boot.[ main] o.w.springframework.339 seconds http://tahe.MySQLDialect 33.150 INFO 4064 --.StandardService : Starting service Tomcat 17.[ost-startStop-1] o.[ost-startStop-1] o.w.839 INFO 4064 --.C.Final} 25.936 INFO 4064 --.C. 2014-01-08 14:47:03.resource.[ost-startStop-1] s.ejb.h.[ost-startStop-1] s.properties not found 27. 2014-01-08 14:47:05.headers=[].0.cfg.springframework.[localhost].s. 2014-01-08 14:47:04.[ost-startStop-1] o.[ost-startStop-1] o.custom=[]}" onto public java.311 INFO 4064 --.internal.095 INFO 4064 --.[ost-startStop-1] o.m.0.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistenceunit 'default' 24.StandardEngine : Starting Servlet Engine: Apache Tomcat/7. 2014-01-08 14:47:03.m.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.handler. .138 INFO 4064 --.double.Version : HHH000412: Hibernate Core {4.[ost-startStop-1] o.394 INFO 4064 --.apache.a.connection.core.produces=[].lang. 2014-01-08 14:47:04.734 INFO 4064 --.apache.jdbc.Final} 26. 2014-01-08 14:47:03.web.hibernate.[ main] org.produces=[].servlet. [/] : Initializing Spring embedded WebApplicationContext 19.Environment : HHH000206: hibernate.s.Version : HCANN000001: Hibernate Commons Annotations {4.m. 2014-01-08 14:47:06.Dialect : HHH000400: Using dialect: org.servlet.1.springframework. 2014-01-08 14:47:04.String.servlet.Rest.servlet.s.i.311 INFO 4064 --.getSalaire(java.custom=[]} " onto public java.t.ResourceHttpRequestHandler] 39.ConnectionProviderInitiator : HHH000130: Instantiating explicit connection provider: org.consumes=[].c.consumes=[].c.892 INFO 4064 --.h.servlet.RequestMappingHandlerMapping : Mapped"{[/employes]. 2014-01-08 14:47:05.http.HttpServletResponse) 37.methods=[GET].h.c.common.TransactionFactoryInitiator : HHH000268: Transaction strategy: org.w.[ost-startStop-1] o.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 22.[ost-startStop-1] org.[ost-startStop-1] o.JdbcTransactionFactory 34.web.hibernate.catalina.resource. 2014-01-08 14:47:05.lang.servlet.[ost-startStop-1] j.Rest. 2014-01-08 14:47:03.392 INFO 4064 --.[Tomcat]. 2014-01-08 14:47:05. 2014-01-08 14:47:03.dialect.144 INFO 4064 --.transaction.javax.ResourceHttpRequestHandler] 38. 2014-01-08 14:47:04.42 18. 2014-01-08 14:47:03.[ost-startStop-1] o.c.w.com 149/157 .a.a. 2014-01-08 14:47:06.getEmployes(javax.i. 2014-01-08 14:47:05. 2014-01-08 14:47:04.Ejb3Configuration : HHH000204: Processing PersistenceUnitInfo [ 29.hibernate.[localhost].ResourceHttpRequestHandler] 23.Environment : HHH000021: Bytecode provider name : javassist 28.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 2942 ms 40.j.context. 2014-01-08 14:47:05.m.core.annotations.521 INFO 4064 --.handler.engine.s.[ost-startStop-1] o.a.2. name: default 30.s.385 INFO 4064 --.String rest.w.Application : Started Application in 6.ico] onto handler of type [class org.s.InjectedDataSourceConnectionProvider 32.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.[ost-startStop-1] org.151 INFO 4064 --. 2014-01-08 14:47:04.int.578 INFO 4064 --.http.HttpServletResponse) 36.s.935 INFO 4064 --.hibernate.handler.RequestMappingHandlerMapping : Mapped "{[/salaire/{SS}/{HeuresTravaillÚes}/ {JoursTravaillÚs}]. 2014-01-08 14:47:05.s.[ost-startStop-1] org.

http://tahe.com 150/157 . salaire : est associée à la chaîne JSON d'une feuille de salaire . 1 sinon .• • ligne 35 : l'URL REST [/employes] est découverte . Le seveur REST travaille avec la base de donnée MySQL5 [dbpam_hibernate] et y accède avec le login [root] sans mot de passe.sql] est fourni : Créez la base de données [dbpam_hibernate] et faites en sorte que le login root sans mot de passe puisse y accéder. employes : est associée à la chaîne JSON de la liste des employés . ligne 36 : l'URL REST [/salaire/{SS}/{HeuresTravaillées}/{JoursTravaillés] est découverte .developpez. • ht : le nombre d'heures travaillées : • jt : le nombre de jours travaillés . Le script de génération de la base de données [dbpam_hibernate. 1 sinon . Testez les deux URL du service REST (après avoir lancé MySQL) : L'URL [/employes] renvoie une chaîne JSON avec deux clés : • • erreur : vaut 0 si pas d'erreur. Elle renvoie une chaîne JSON avec deux clés : • • erreur : vaut 0 si pas d'erreur. L'URL [/salaire] attend trois paramètres [SS/ht/jt] : • SS : le n° de sécurité sociale de l'employé dont on veut la feuille de salaire .

on indique un nombre d'heures . . on donne l'URL du service REST. en [2].Ici a été utilisé un n° SS erroné. Mettez l'adresse IP wifi de la machine du serveur REST .developpez. 17. message : le message de l'erreur Voici un autre exemple où on a arrêté le SGBD : Le client Android a pour objet de récupérer les informations renvoyées par le serveur REST et de les mettre en forme.3 Les vues du client Android Les différentes vues du client Android sont les suivantes : Il faut tout d'abord se connecter au service REST : 2 1 • • en [1]. On arrive alors à la page de simulation : 6 4 5 3 • • en [3]. http://tahe.com 151/157 . on choisit un employé . L'URL [/salaire] renvoie une chaîne JSON avec deux clés : • • erreur : à 1 parce qu'il y a une erreur . on se connecte . en [4].


en [5], on indique un nombre de jours ;
en [6], on demande la simulation ;

La page de simulation obtenue est la suivante :

8

7


en [7], la simulation obtenue ;
en [8], on l'enregistre ;

9

10


en [9], la liste des simulations ;
en [10], on retire une simulation ;

12

11


en [11], il n'y a plus de simulations ;
en [12], on retourne au formulaire de simulation ;

http://tahe.developpez.com

152/157

14

13


en [13], on retrouve un formulaire vide ;
en [14], on termine la session de simulations ;

15

17.4

en [15], on retrouve le formulaire de connexion initial.

Travail à faire

Réalisez cette application. Vous trouverez dans le dossier des exemples divers éléments pour vous aider. Tout d'abord, le binaire du
client Android décrit ci-dessus vous est donné :





reliez la tablette au PC. La tablette est reconnue comme un disque supplémentaire et son système de fichiers est
accessible ;
transférez le fichier [apk] ci-dessus sur la tablette ;
sur la tablette, utilisez le gestionnaire de fichiers pour retrouver le fichier que vous venez de copier ;
installez l'application en touchant le fichier [apk] ;
l'application se retrouve désormais avec les autres applications de la tablette. Pour la lancer, touchez son icône.

L'autre élément pour vous aider est le squelette du client Android présenté précédemment.

http://tahe.developpez.com

153/157

Vous y trouverez notamment :


le projet de la couche [DAO] : [client-pam-dao-skel]. Il est complet ;
le projet de la couche [métier] : [client-pam-metier-skel]. Il est à compléter ;
le projet de la couche [présentation] : [client-pam-android-skel]. Il est à compléter.

Le projet qui vous est donné est exécutable. Il a déjà les vues nécessaires. Il y a simplement du code à rajouter pour que l'application
fasse ce qu'elle a à faire.
Les données affichées par les différentes vues proviennent du serveur REST :

1
La liste déroulante [1] est remplie avec des informations provenant de l'URL [localhost:8080/employes] :

http://tahe.developpez.com

154/157

com 155/157 .La page de simulation : 2 est remplie avec les informations provenant de 'URL [localhost:8080/salaire] : La page des simulations : http://tahe.developpez.

Celles-ci doivent donc être mémorisées.3 est obtenue à partir d'une liste des simulations faites par l'utilisateur.developpez.com 156/157 . L'information [3] est la somme de deux informations de la feuille de salaire : http://tahe.

developpez.18 Conclusion Ce document permet de démarrer la programmation Android mais pas de la maîtriser.android. http://tahe.html].developpez.com/android/avat] qui propose un modèle générique pour gérer les tâches asynchrones d'une application Android et qui l'applique à une application client / serveur de gestion de rendez-vous.com 157/157 .com/guide/components/index. On pourra également consulter le document [http://tahe. on consultera le site de référence pour la programmation Android [http://developer. Pour cette maîtrise.