Android pour les développeurs J2EE

Un modèle asynchrone pour les clients web


précédentsommairesuivant

X. AVAT- Exemple 7

Revenons à l'architecture la plus générale des clients Android présentés jusqu'à maintenant, celle de l'exemple 2 :

Image non disponible

La couche [Actions] est facultative dans tous les cas . On peut déporter la logique qu'on y trouve dans la vue. La vue [Vue] et l'action [Action] sont en effet tous les deux des boss [IBoss]. A ce titre, la vue peut employer des travailleurs [IWorker] quelconques dont les tâches asynchrones [Task]. Nous allons étudier un client Android plus complexe que les précédents avec cette architecture simplifiée :

Image non disponible

Nous appelerons cette architecture AVT (Activité-Vues-Tâches).

X-A. Le projet

Dans le document " Introduction aux frameworks JSF2, Primefaces et Primefaces mobile " [http://tahe.developpez.com/java/primefaces/], on étudie une application de prise de rendez-vous pour des médecins. On y développe trois types d'interfaces :

  • une interface web JSF2 ;
  • une interface web Primefaces ;
  • une interface web mobile pour smartphones avec Primefaces Mobile ;

Par la suite nous référencerons ce document [pfm] (Primefaces Mobile). Le projet sera ici de créer, sur tablette Android, une interface analogue aux précédentes.

L'interface JSF2 était la suivante :

La page d'accueil

Image non disponible

A partir de cette première page, l'utilisateur (Secrétariat, Médecin) va engager un certain nombre d'actions. Nous les présentons ci-dessous. La vue de gauche présente la vue à partir de laquelle l'utilisateur fait une demande, la vue de droite la réponse envoyée par le serveur.

Image non disponible
Image non disponible
Image non disponible
Image non disponible
Image non disponible
Image non disponible

Enfin, on peut également obtenir une page d'erreurs :

Image non disponible

L'application Android va présenter des écran analogues. Nous ne gérerons pas l'internationalisation de l'application mais nous faciliterons celle-ci en déportant les textes affichés dans des fichiers.

Il y aura cinq écrans :

Ecran de configuration

Image non disponible

Ecran de choix du médecin du rendez-vous

Image non disponible

Ecran de choix du créneau horaire du rendez-vous

Image non disponible

Ecran de choix du client du rendez-vous

Image non disponible

Après la prise de rendez-vous :

Image non disponible

X-B. L'architecture du projet

On aura une architecture client / serveur analogue à celle de l'exemple 2 de ce document :

Le serveur

Image non disponible
  • nous allons réutiliser le travail fait dans [pfm] : les couches [métier] et [DAO], les entités JPA, la base de données MySQL ;
  • notre travail consistera à exposer au monde web, via un service REST, l'interface de la couche [métier].

Le client Android

L'architecture de celui-ci a déjà été présentée :

Image non disponible

Pour développer ce client, nous nous appuierons sur ce qui a été fait dans l'exemple 2 de ce document.

X-C. La base de données

Elle ne joue pas un rôle fondamental dans ce document. Nous la donnons à titre d'information.

On l'appellera [brdvmedecins2]. C'est une base de données MySQL5 avec quatre tables :

Image non disponible

X-C-1. La table [MEDECINS]

Elle contient des informations sur les médecins gérés par l'application [RdvMedecins].

Image non disponible

Image non disponible

  • ID : n° identifiant le médecin - clé primaire de la table
  • VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne.
  • NOM : le nom du médecin
  • PRENOM : son prénom
  • TITRE : son titre (Melle, Mme, Mr)

X-C-2. La table [CLIENTS]

Les clients des différents médecins sont enregistrés dans la table [CLIENTS] :

Image non disponible

Image non disponible

  • ID : n° identifiant le client - clé primaire de la table
  • VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne.
  • NOM : le nom du client
  • PRENOM : son prénom
  • TITRE : son titre (Melle, Mme, Mr)

X-C-3. La table [CRENEAUX]

Elle liste les créneaux horaires où les RV sont possibles :

Image non disponible

Image non disponible
  • ID : n° identifiant le créneau horaire - clé primaire de la table (ligne 8)
  • VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne.
  • ID_MEDECIN : n° identifiant le médecin auquel appartient ce créneau - clé étrangère sur la colonne MEDECINS(ID).
  • HDEBUT : heure début créneau
  • MDEBUT : minutes début créneau
  • HFIN : heure fin créneau
  • MFIN : minutes fin créneau

La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le créneau n° 2 commence à 8 h 20 et se termine à 8 h 40 et appartient au médecin n° 1 (Mme Marie PELISSIER).

X-C-4. La table [RV]

Elle liste les RV pris pour chaque médecin :

Image non disponible
  • ID : n° identifiant le RV de façon unique - clé primaire
  • JOUR : jour du RV
  • ID_CRENEAU : créneau horaire du RV - clé étrangère sur le champ [ID] de la table [CRENEAUX] - fixe à la fois le créneau horaire et le médecin concerné.
  • ID_CLIENT : n° du client pour qui est faite la réservation - clé étrangère sur le champ [ID] de la table [CLIENTS]

Cette table a une sur les valeurs des colonnes jointes (JOUR, ID_CRENEAU) :

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

Si une ligne de la table[RV] a la valeur (JOUR1, ID_CRENEAU1) pour les colonnes (JOUR, ID_CRENEAU), cette valeur ne peut se retrouver nulle part ailleurs. Sinon, cela signifierait que deux RV ont été pris au même moment pour le même médecin. D'un point de vue programmation Java, le pilote JDBC de la base lance une SQLException lorsque ce cas se produit.

La ligne d'id égal à 3 (cf [1] ci-dessus) signifie qu'un RV a été pris pour le créneau n° 20 et le client n° 4 le 23/08/2006. La table [CRENEAUX] nous apprend que le créneau n° 20 correspond au créneau horaire 16 h 20 - 16 h 40 et appartient au médecin n° 1 (Mme Marie PELISSIER). La table [CLIENTS] nous apprend que le client n° 4 est Melle Brigitte BISTROU.

X-C-5. Génération de la base

Pour créer les tables et les remplir on pourra utiliser le script [dbrdvmedecins2.sql] qu'on trouvera sur le site des exemples.

Image non disponible

Avec [WampServer] (cf paragraphe , page ), on pourra procéder comme suit :

Image non disponible
  • en [1], on clique sur l'icône de [WampServer] et on choisit l'option [PhpMyAdmin] [2],
  • en [3], dans la fenêtre sui s'est ouverte, on sélectionne le lien [Bases de données],Image non disponible
  • en [2], on crée une base de données dont on a donné le nom [4] et l'encodage [5],
  • en [7], la base a été créée. On clique sur son lien,Image non disponible
  • en [8], on importe un fichier SQL,
  • qu'on désigne dans le système de fichiers avec le bouton [9],Image non disponible
  • en [11], on sélectionne le script SQL et en [12] on l'exécute,
  • en [13], les quatre tables de la base ont été créées. On suit l'un des liens,Image non disponible
  • en [14], le contenu de la table.

Par la suite, nous ne reviendrons plus sur cette base. Mais le lecteur est invité à suivre son évolution au fil des tests surtout lorsque ça ne marche pas.

X-D. Les projets Eclipse de l'application

L'application regroupe plusieurs projets Eclipse, tous des projets Maven :

Image non disponible
  • [android-avat] : le modèle AVAT. Fait ;
  • [server-rdvmedecins-entities] : les entités JPA du serveur. Fait. Elles sont tirées du document [pfm] ;
  • [server-rdvmedecins-dao] : la couche [DAO] du serveur. Fait. C'est celle du document [pfm] ;
  • [server-rdvmedecins-metier] : la couche [métier] du serveur. Fait. C'est celle du document [pfm] ;
  • [server-rdvmedecins-jsf] : la couche [JSF2] du serveur. Fait. C'est celle du document [pfm]. Elle sert de point de repère pour notre application. On essaie de reproduire son fonctionnement ;
  • [server-rdvmedecins-rest] : la couche [REST] du serveur. A faire;
  • [android-rdvmedecins-entities] : les entités du client. Ce sont celles du serveur débarrassées de leurs annotations JPA ;
  • [android-rdvmedecins-dao] : la couche [DAO] du client Android. Ce sera celle de l'exemple 2 ;
  • [android-rdvmedecins-metier] : la couche [métier] du client Android. A faire ;
  • [android-rdvmedecins-ui] : la couche [présentation] du client Android. A faire ;

X-E. La couche [métier] du serveur

Revenons à l'architecture du serveur :

Image non disponible

Nous devons écrire la couche [REST] qui expose au monde web la couche [métier]. Celle-ci présente l'interface [IMetier] suivante :

 
CacherSélectionnez
  • les méthodes des lignes 16 à 43 délèguent leur exécution à la couche [DAO]. Ce sont des méthodes d'accès aux données du SGBD ;
  • ligne 46 : la seule méthode appartenant véritablement à la couche [métier].

Nous allons exposer ces méthodes via un service REST en changeant parfois leur signature afin de faciliter les échanges avec les clients. On suivra la règle suivante : plutôt que d'échanger un objet JPA avec une clé primaire, on échangera simplement la clé primaire et on ira chercher l'objet en base.

 :

La classe d'exception [RdvMedecinsException] : elle est lancée par les méthodes de la couche [métier]

 
CacherSélectionnez

L'entité JPA [Personne] : représente un médecin ou un client. On ne reproduit pas les annotations JPA afin d'alléger le code.

 
CacherSélectionnez

La classe [Medecin] : représente un médecin

 
CacherSélectionnez

La classe [Client] : représente un client

 
CacherSélectionnez

La classe [Creneau] : représente un créneau horaire.

 
CacherSélectionnez

La classe [Rv] : représente un rendez-vous.

 
CacherSélectionnez

La classe [AgendaMedecinJour] : l'agenda d'un médecin pour un jour donné.

 
CacherSélectionnez

La classe [CreneauMedecinJour] : représente l'occupation d'un créneau horaire.

 
CacherSélectionnez

X-F. Le serveur REST

Image non disponible

Le service REST est implémenté par SpringMVC. Un service REST (RepresEntational State Transfer) est un service HTTP répondant aux demandes GET, POST, PUT, DELETE d'un client HTTP. Sa définition formelle indique pour chacune de ces méthodes, les objets que le client doit envoyer et celui qu'il reçoit. Dans les exemples de ce document, nous n'utilisons que la méthode GET alors que dans certains cas, la définition formelle de REST indique qu'on devrait utiliser une méthode PUT ou DELETE. 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.

X-F-1. Le projet Eclipse

Le projet Eclipse du serveur REST est le suivant :

Image non disponible
  • en [1], le projet dans son ensemble. C'est un projet Maven de type web ;
  • en [2], les dépendances Maven du projet. Elles sont très nombreuses. Le gros des archives est fourni par Hibernate qui assure la persistance des données en base et Spring qui assure l'intégration des couches ainsi que la gestion des transactions ;
  • en [3], le projet présente des dépendances sur les couches [DAO] et [métier] du serveur ainsi que sur ses entités ;
  • en [4], la couche REST est assurée par une classe.

X-F-2. Les dépendances Maven

Image non disponible

Le fichier [pom.xml] est le suivant :

 
CacherSélectionnez
  • lignes 29-48 : les dépendances sur Spring ;
  • lignes 49-53 : la dépendance sur la bibliothèque JSON Jackson ;
  • lignes 54-58 : la dépendance sur l'API servlet ;
  • lignes 59-63 : la dépendance sur la couche [métier] du serveur.

X-F-3. Configuration de la couche [REST]

Le service REST est configuré de façon analogue à celui de l'exemple 2.

Image non disponible

X-F-3-a. Le fichier [web.xml]

Le fichier [web.xml] est le suivant :

 
CacherSélectionnez

Il est identique à celui de l'exemple 2 (page , paragraphe ).

X-F-3-b. Le fichier [rest-services-config.xml]

Le fichier [rest-services-config.xml] est le suivant :

 
CacherSélectionnez
  • lignes 16-56 : configurent les couches [métier], [DAO], [JPA] du serveur. Cette configuration est expliquée dans le document [pfm] ;
  • ligne 36-42 : définissent la source de données, la base MySQL. Pour ses propres tests, le lecteur sera peut être amené à changer ces informations ;
  • lignes 58-82 : configurent le service REST. La configuration est analogue à ce qu'elle était dans l'exemple 2 (page , paragraphe ).

X-F-4. Implémentation de la couche [REST]

Image non disponible
Image non disponible

La couche REST est implémentée par la classe [Rest] suivante :

 
CacherSélectionnez

La classe suit les principes utilisés dans l'exemple 2 (page , paragraphe ).

  • ligne 15 : l'URL permettant d'obtenir la listes des clients ;
  • ligne 21 : l'URL permettant d'obtenir la listes des médecins ;
  • ligne 27 : l'URL permettant d'obtenir la listes des créneaux horaires d'un médecin. Le paramètre est l'identifiant du médecin ;
  • ligne 33 : l'URL permettant d'obtenir la liste des rendez-vous d'un médecin pour un jour donné. Les paramètres sont :
  • l'identifiant du médecin,
  • le jour ;
  • ligne 39 : l'URL permettant d'obtenir un client identifié par son numéro ;
  • ligne 46 : idem pour un médecin ;
  • ligne 52 : idem pour un rendez-vous ;
  • ligne 58 : idem pour un créneau horaire ;
  • ligne 63 : l'URL pour ajouter un rendez-vous. Les paramètres passés dans l'URL sont :
  • le jour du rendez-vous,
  • le n° du créneau horaire du rendez-vous,
  • le n° du client pour qui le rendez-vous est pris ;
  • ligne 70 : l'URL pour supprimer un rendez-vous identifié par son n° ;
  • ligne 76 : l'URL pour obtenir l'agenda d'un médecin pour un jour donné. Les paramètres de l'URL sont :
  • le n° du médecin,
  • le jour ;
  • ligne 82 : une méthode privée qui génère une réponse en cas d'exception.

Voyons maintenant le code de ces méthodes.

Note : pour obtenir les copies d'écran ci-dessous, le SGBD doit être lancé.

X-F-4-a. Liste des clients

Le code est le suivant :

 
CacherSélectionnez
  • ligne 2 : l'URL n'a pas de paramètres ;
  • ligne 7 : la liste des clients est demandée à la couche [métier] ;
  • ligne 9 : la réponse renvoyée au client. Le paramétrage du serveur REST étant le même que celui de l'exemple 2, le client recevra une réponse JSON contenant la liste des clients (voir copie d'écran ci-dessous) ;
  • ligne 12 : la réponse envoyée en cas d'exception.

La méthode [createResponseError] est la méthode chargée d'envoyer la réponse au client en cas d'exception. Elle a deux paramètres :

  • le n° de l'erreur,
  • le message de l'erreur ;

Son code est le suivant :

 
CacherSélectionnez

Le client recevra une chaîne JSON de la forme :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-b. Liste des médecins

Le code est analogue :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-c. Liste des créneaux d'un médecin

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution (vue partielle) :

Image non disponible

X-F-4-d. Liste des rendez-vous d'un médecin

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-e. Obtenir un client identifié par son n°

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-f. Obtenir un médecin identifié par son n°

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-g. Obtenir un rendez-vous identifié par son n°

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-h. Obtenir un créneau horaire identifié par son n°

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-i. Ajouter un rendez-vous

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

On notera :

  • le n° (2) du rendez-vous créé ;
  • le format JSON du jour. Cela nous posera ultérieurement des problèmes car le client Gson ne s'attend pas à recevoir une date sous cette forme. Il doit être possible de fixer la forme JSON d'une date. Cela n'a pas été fait ici.

X-F-4-j. Supprimer un rendez-vous

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible

X-F-4-k. Obtenir l'agenda d'un médecin

Le code est le suivant :

 
CacherSélectionnez

Voici un exemple d'exécution :

Image non disponible
  • en [1], le format JSON du jour de l'agenda ;
  • en [2], le format JSON du jour d'un rendez-vous. Ce ne sont pas les mêmes. Il faudra gérer ces différences côté client.

X-G. La couche [DAO] du client Android

Image non disponible

X-G-1. Le projet Eclipse

Le projet Eclipse de la couche [DAO] du client Android est le suivant :

Image non disponible
  • en [1], le projet Maven de la couche [DAO] a une dépendance sur le projet [android-rdvmedecins-entities]. Ce projet rassemble les entités décrites page , paragraphe  ;
  • en [2], la couche [DAO] du client Android.

L'interface [IDao] est la suivante :

 
CacherSélectionnez

Cette interface et son implémentation sont identiques à ce qu'elles étaient dans la couche [DAO] de l'exemple 2 page , paragraphe .

X-H. La couche [métier] du client Android

Image non disponible

X-H-1. Le projet Eclipse

Le projet Eclipse de la couche [métier] du client Android est le suivant :

Image non disponible
  • en [1], le projet Maven de la couche [métier] a une unique dépendance, celle sur la couche [DAO] du client. Les autres apparaissent par cascade ;
  • en [2], la couche [métier] du client Android.

X-H-2. Implémentation

L'interface [IMetier] est la suivante :

 
CacherSélectionnez
  • l'interface [IMetier] du client Android correspond aux méthodes exposées par le service REST du serveur. On rappelle ainsi pour chaque méthode, l'URL du service REST visée.

L'implémentation [Metier] de cette interface est la suivante :

 
CacherSélectionnez
  • ligne 8 : une référence sur la couche [DAO]. Sera instanciée par la fabrique du modèle AVT ;
  • ligne 10 : la bibliothèque JSON utilisée côté client Android est la bibliothèque Gson de Google ;
  • ligne 12 : l'URL du service REST interrogé par le client Android. Est initialisée lorsque l'utilisateur donne cette information ;
  • ligne 15 : la méthode d'obtention de la liste des clients ;
  • ligne 17 : construction de l'URL complète de la méthode REST à appeler ;
  • ligne 19 : on demande à la couche [DAO] d'appeler cette méthode. On rappelle les quatre paramètres de la méthode [executeRestService] de la couche [DAO] :
  • la méthode " get " ou " post " de la requête HTTP à émettre,
  • l'URL complète de la méthode REST à exécuter,
  • un dictionnaire des données transmises par une opération HTTP POST. Donc null ici, puisqu'on fait une opération HTTP GET,
  • sous la forme d'un dictionnaire, les valeurs des variables de l'URL. Ici il n'y a pas de variables dans l'URL. On fournit alors un dictionnaire vide. Il ne peut être null ;

On ne gère pas d'exception. La couche [DAO] lance des exceptions non contrôlées. Elles vont remonter toutes seules jusqu'à la couche [Présentation] assurée par le modèle AVAT ;

  • ligne 22 : on reçoit de la couche [DAO] une chaîne JSON représentant une liste de clients. Cette chaîne est alors utilisée pour créer un objet de type List<Client> (ligne 15) ;
  • ligne 25 : au cas où la chaîne n'a pu être transformée en un objet de type List<Client> une exception est lancée.

Toutes les méthodes de la couche [métier] suivent la même logique :

 
CacherSélectionnez

Prenons par exemple la méthode [getAllCreneaux]

 
CacherSélectionnez
  • ligne 1 : l'URL interrogée. Elle a une variable, le n° [idMedecin] du médecin ;
  • ligne 5 : l'URL complète est construite ;
  • lignes 7-8 : un dictionnaire est construit pour affecter des valeurs aux variables de l'URL, ici une seule variable ;
  • ligne 10 : la requête au service REST est faite par la couche [DAO] ;
  • ligne 13 : on rend la réponse, ici un objet de type List<Creneau> ;
  • ligne 16 : si la chaîne JSON n'a pu être convertie en un objet de type List<Creneau> une exception est lancée.

Nous n'allons pas décrire la totalité des méthodes. Le lecteur intéressé les trouvera dans les codes source qui accompagnent ce document. Certaines méthodes ont été plus difficiles à écrire que d'autres à cause de la représentation JSON de l'objet de type [java.util.Date] envoyée par la bibliothèque Jackson du serveur. Elle n'a pas été reconnue par la bibliothèque Gson du client Android et on avait alors une exception. Dans ces cas, il a fallu procéder en trois temps :

  • désérialiser la chaîne JSON reçue en un objet Map<String,Object> ;
  • remplacer dans ce dictionnaire les jours par le bon format ;
  • resérialiser le nouveau dictionnaire en l'objet T que doit rendre la méthode.

C'est assez lourd et présente peu d'intérêt mais c'est un bon exercice de sérialisation / désérialisation d'un objet JSON.

X-I. La couche [AVT]

On aborde ici la couche AVT (Activité - Vues - Tâches) du client Android.

Image non disponible

X-I-1. Le projet Eclipse

Le projet Eclipse est le suivant :

Image non disponible
  • en [1], un projet Maven de type Android ;
  • en [2] les dépendances Maven. Il y en a deux :
  • celle sur le modèle [AVT] ;
  • celle sur la couche [métier] que nous venons d'écrire ;

Les autres dépendances découlent des deux précédentes.

  • en [3], les codes source :
  • en [3] : l'activité, la fabrique, la configuration et la session ;
  • en [4] : les tâches asynchrones ;
  • en [5] : les vues.

X-I-2. L'activité Android

Image non disponible

La classe [MainActivity] est la suivante :

 
CacherSélectionnez

Elle a les fonctionnalités suivantes :

  • ligne 21 : créer la fabrique d'objets en lui passant la classe de configuration de l'application ;
  • lignes 23-25 : afficher la première vue. Celle-ci est la suivante :
Image non disponible

X-I-3. La classe de configuration

Image non disponible

La classe [Config] est la suivante :

 
CacherSélectionnez

Elle est identique à ce qu'elle était dans l'exemple 2.

X-I-4. Le patron des vues

Toutes les vues auront le format précédent imposé par le fichier [main.xml] de l'activité :

Image non disponible

Le fichier [main.xml] est le suivant :

 
CacherSélectionnez

et correspond à la vue suivante :

Image non disponible

X-I-5. La vue [Config]

Image non disponible

C'est la première vue affichée. Elle est composée des éléments suivants :

Image non disponible

Id

Type

Rôle

1

edtUrlServiceRest

EditText

URL du service REST

2

btnValider

Button

tente une connexion au serveur REST

2

btnAnnuler

Button

annule la connexion

Le code de la vue est le suivant :

 
CacherSélectionnez
  • ligne 5 : la vue dérive de la vue [LocalVue]. Cette vue sera le parent de toutes les vues de l'application. On y rassemble tout ce qui peut être factorisé entre vues ;
  • lignes 30-38 : la gestion du clic sur le bouton [Annuler]. C'est celui désormais classique de tous les exemples vus dans ce document. Nous n'y reviendrons plus ;
  • lignes 46-51 : la méthode [onResume] est exécutée juste avant l'affichage de la vue. On l'utilisera dans chaque vue pour deux choses :
  • récupérer des informations dans la session afin de les afficher ;
  • activer certains liens du bandeau gauche.
  • ligne 50 : la méthode activateLinks(int i) appartient à la classe parent [LocalVue]. Elle fait en sorte que les liens du bandeau gauche de n° <i soient cliquables et de couleur bleue, les autres étant désactivés et de couleur noire. Cela permet à l'utilisateur de revenir en arrière dans l'assistant de prise de rendez-vous. activateLinks(0) va ici désactiver tous les liens.
  • ligne 57 : on instancie la tâche qui va demander la liste des médecins au serveur REST. Cette liste restera en session ;
  • ligne 61 : la tâche est lancée ;
  • lignes 60-63 : on lance une seconde tâche, cette fois pour obtenir la liste des clients qui sera mémorisée elle aussi en session ;
  • ligne 65 : début de l'attente ;
  • ligne 67 : on surveille la fin de ces deux tâches ;
  • ligne 110 : la vue [Config] va voir passer les notifications des deux tâches lancées ;
  • lignes 114-116 : dans le cas d'une notification [WORK_INFO], on mémorise l'information dans la liste d'objets de la ligne 14. Pour chacune des deux tâches, l'information enregistrée est un dictionnaire :
  • avec la clé " client " et une valeur de type List<Client> pour la liste des clients,
  • avec la clé " medecin " et une valeur de type List<Medecin> pour la liste des médecins ;
  • ligne 75 : on est prévenu de la fin des deux tâches ;
  • ligne 77 : on annule l'attente ;
  • ligne 81 : on exploite les deux informations stockées dans la liste des résultats ;
  • ligne 83 : on regarde si le résultat obtenu est bien un dictionnaire. Si oui, il est mis dans la session (lignes 84-89). Sinon on regarde si le résultat est une exception (ligne 90). Si oui, elle est affichée (ligne 92) par une méthode de la classe [LocalVue] ;
  • ligne 99 : s'il n'y a pas eu d'erreurs, la liste des clients et celle des médecins sont maintenant en session. C'est là qu'iront les chercher les vues qui en ont besoin ;
  • lignes 101-103 : s'il n'y a pas eu d'erreur, la vue suivante de l'assistant est affichée ;

On notera les points suivants :

  • on trouve désormais un peu de logique dans la vue (lignes 56-63). La vue doit " savoir " qu'elle doit appeler deux tâches asynchrones et dans quel ordre. Auparavant, cette connaissance était dans l'action. On s'écarte un peu de la notion de séparation des tâches (separation of concern) mais peu. Il y aura toujours très peu de logique dans la vue, celle-ci étant concentrée dans la couche [métier] ;
  • lignes 56-63, on appelle deux tâches asynchrones qui vont s'exécuter en parallèle. On aurait pu écrire une méthode dans la couche [métier] pour récupérer les deux listes, médecins et clients. Une tâche asynchrone aurait alors suffi pour l'exécuter et on aurait mieux atteint l'objectif de séparation des tâches. Mais dans ce cas les deux listes auraient été obtenues l'une après l'autre et non pas en parallèle.

X-I-6. La vue [LocalVue]

Image non disponible

La vue [LocalVue] est parente de toutes les vues de l'application. On y a rassemblé tout ce qui pouvait être factorisé entre vues. Son code est le suivant :

 
CacherSélectionnez
  • ligne 5 : [LocalVue] étend la vue abstraite [Vue] qui implémente l'interface [IVue] ;
  • ligne 65 : la méthode abstraite [notifyEndOfTasks] est implémentée par les classes filles ;
  • ligne 56 : pour afficher le sablier ;
  • ligne 60 : pour le cacher ;
  • ligne 49 : la méthode d'affichage des exceptions qui remontent jusqu'aux vues. On utilise une boîte de dialogue telle que la suivante :Image non disponible
  • ligne 51 : on utilise la classe [ExceptionVue] suivante :
 
CacherSélectionnez
  • ligne 6 : la classe étend [DialogFragment] qui permet d'afficher des boîtes de dialogue ;
  • lignes 16-18 : le constructeur reçoit en paramètre l'exception à afficher ;
  • ligne 21 : la méthode [onCreateDialog] est exécutée à la création du dialogue avant son affichage ;
  • lignes 23-28 : on construit le message que l'on va afficher dans la boîte de dialogue. C'est la concaténation des messages d'erreur de toutes les exceptions contenues dans l'exception passée au constructeur ;
  • ligne 30 : on construit une boîte de dialogue prédéfinie [AlertDialog] ;
  • ligne 31 :
  • setTitle : fixe le titre de la boîte de dialogue [1] ;
  • setMessage : fixe le message que la boîte de dialogue va afficher [2] ;
  • setPositiveButton : fixe la nature du bouton qui va permettre de fermer la boîte de dialogue [3] ;Image non disponible
  • ligne 38 : on doit rendre la boîte de dialogue qui vient d'être créée.

Revenons au code de [showException] :

 
CacherSélectionnez
  • ligne 4 : la boîte de dialogue est instanciée mais pas créée ;
  • ligne 5 : la boîte est créée et affichée.

Revenons au code de [LocalVue] :

 
CacherSélectionnez
  • ligne 3 : la session qui sert aux échanges d'informations entre vues ;
  • ligne 5 : une référence sur l'activité Android qui sous-tend les vues ;
  • lignes 7-10 : les quatre liens du bandeau gauche des vues :Image non disponible
  • ligne 11 : un tableau contenant ces quatre liens ;
  • ligne 22 : récupère une référence sur la session (singleton) ;
  • ligne 24 : récupère la nature exacte de l'activité. activity est une référence injectée dans la classe abstraite [Vue] dont dérive [LocalVue] et est de type [Activity]. Afin d'éviter des transtypages dans le code des vues, on crée un champ mainActivity avec le bon type ;
  • lignes 26-29 : on récupère les références des quatre liens ;
  • ligne 31 : on les stocke dans le tableau.

L'activation / désactivation des liens se fait avec la méthode suivante :

 
CacherSélectionnez
  • ligne 2 : le paramètre de la méthode est un n° de lien. La méthode active tous les liens qui ont un n° <idLink et désactivent tous les autres ;
  • ligne 6 : le lien activé sera de couleur bleur ;
  • lignes 7-14 : le clic sur ce lien est géré ;
  • ligne 19 : un lien désactivé sera de couleur noire ;
  • ligne 20 : le clic sur le lien n'est pas géré ;
  • ligne 11 : un clic sur un lien actif va provoquer l'exécution de la méthode [navigateToView] qui va afficher la vue correspondant au lien ;
 
CacherSélectionnez
  • ligne 2 : le paramètre de la méthode est un lien de type [TextView] ;
  • ligne 4 : on récupère le libellé de ce lien ;
  • lignes 6-17 : on compare ce libellé à ceux des quatres liens ;
  • ligne 7 : la classe [MainActivity] a une classe [showVue] permettant d'afficher un objet [Vue]. Cet objet est demandé à la fabrique (singleton).

X-I-7. La fabrique

Image non disponible

La fabrique crée tous les objets du client Android :

 
CacherSélectionnez

Nous donnons ce code seulement pour lister les objets gérés par la fabrique. Leur code de fabrication est analogue à celui des fabriques déjà présentées.

X-I-8. La tâche [GetAllClientsTask]

Image non disponible

Cette tâche fournit la liste des médecins :

 
CacherSélectionnez

Toutes les tâches du client Android sont construites sur le même modèle :

  • lignes 12-15 : la méthode [onPreExecute] est utilisée pour envoyer au boss (la vue) la notification [WORK_STARTED] ;
  • lignes 38-42 : la méthode [onPostExecute] est utilisée pour envoyer au boss (la vue) la notification [WORK_INFO] avec le résultat de la tâche et la notification [WORK_TERMINATED] ;
  • ligne 20 : on demande à la fabrique une référence sur la couche [métier] ;
  • lignes 21-31 : on exécute la méthode adéquate de la couche [métier] ;
  • lignes 25-27 : l'information produite par la tâche est un dictionnaire avec une unique entrée de clé "clients" et de valeur la liste des clients de type List<Client>.

X-I-9. La tâche [GetAllMedecinsTask]

Image non disponible

Cette tâche fournit la liste des médecins sous la forme d'un dictionnaire avec une unique entrée de clé "medecins" et de valeur la liste des clients de type List<Medecin>, ceci de façon similaire à [GetAllClientsTask].

X-I-10. La vue [AccueilVue]

Image non disponible

C'est la vue suivante :

Image non disponible

Id

Type

Rôle

1

spinnerMedecins

Spinner

liste déroulante des médecins

2

edtJourRv

TextView

la date du rendez-vous au format JJ-MM-AAAA

3

btnValider

Button

affiche l'agenda du médecin choisi

3

btnAnnuler

Button

annule la demande

Le code de la vue est le suivant :

 
CacherSélectionnez
  • ligne 1 : avant de s'afficher la vue doit mettre à jour son modèle avec des informations trouvées dans la session ;
  • ligne 5 : la liste des médecins est récupérée en session. La session est injectée dans les vues et les tâches par la fabrique lors de leur création ;
  • lignes 8-12 : un tableau [arrayMedecin] de chaînes de la forme [Mr Paul Marand] est construit ;
  • lignes 14-17 : ce tableau sera le contenu de la liste déroulante [spinnerMedecins] ;
  • ligne 19 : le 1er lien du bandeau gauche est activé. Il permet à l'utilisateur de revenir à la vue [ConfigVue] précédente.
 
CacherSélectionnez
  • ligne 2 : la méthode exécutée lors d'un clic sur le bouton [Valider] ;
  • ligne 8 : les résultats des tâches asynchrones sont stockées dans une liste d'objets [results]. Avant chaque nouvelle exécution, on nettoie cette liste ;
  • lignes 10-13 : avec les informations saisies (médecin et jour), on lance la tâche asynchrone [GetAgendaMedecinTask] qui crée l'agenda du médecin ;
  • ligne 15 : début attente visuelle ;
  • ligne 17 : début attente de la fin des tâches.
 
CacherSélectionnez
  • ligne 3 : on traite les notifications envoyées par la tâche lancée ;
  • ligne 5 : la notification est d'abord passée au parent ;
  • lignes 7-8 : l'information tarnsportée par la notification [WORK_INFO] est mémorisée ;
 
CacherSélectionnez
  • ligne 2 : la méthode exécutée à la fin de la tâche ;
  • ligne 4 : fin de l'attente visuelle ;
  • ligne 7 : on scanne la liste des résultats. On n'en attend qu'un donc on peut se passer de la boucle ;
  • lignes 9-11 : si le résultat est l'agenda attendu, il est mémorisé dans la session ;
  • lignes 12-14 : si le résultat est une exception, celle-ci est affichée ;
  • lignes 21-24 : s'il n'y pas eu d'erreur, on affiche la vue suivante [AgendaVue].

X-I-11. La tâche [GetAgendaMedecinTask]

Image non disponible

La tâche rend l'agenda du médecin. Son code est le suivant :

 
CacherSélectionnez
  • ligne 3 : la vue a passé les paramètres (N° du médecin, Jour du rendez-vous) ;
  • ligne 12 : l'information rendue est de type [AgendaMedecinJour].

X-I-12. La vue [AgendaVue] - 1

Image non disponible

C'est la vue suivante :

Image non disponible

Id

Type

Rôle

1

lstCreneaux

ListView

liste des créneaux horarires des médecins

2

txtTitre2

TextView

une ligne d'information

3

 

TextView

lien pour ajouter un rendez-vous

4

 

TextView

lien our supprimer un rendez-vous

Le code de la vue est le suivant :

 
CacherSélectionnez
  • ligne 19 : méthode exécutée juste avant l'affichage de la vue ;
  • ligne 23 : on récupère l'agenda du médecin en session ;
  • lignes 25-28 : on génère la ligne d'information [2] ;
  • lignes 30-32 : on initialise le ListView [1] via un adaptateur propriétaire [ListCreneauxAdapter] auquel on passe les paramètres suivants :
  • ligne 34 : on positionne le [ListView] au bon endroit. Lorsqu'on supprime un rendez-vous, le [ListView] est régénéré totalement à partir de la base de données afin de prendre en compte les éventuelles modifications apportées en base par d'autres utilisateurs. On veut alors réafficher le [ListView] dans la position où il était lorsque l'utilisateur a cliqué le lien [Supprimer]. Nous expliquerons comment faire ;
  • ligne 36 : les deux premiers liens du bandeau gauche sont activés permettant à l'utilisateur de revenir à l'une des deux premières vues de l'assistant.
  • l'activité Android en cours,
  • le fichier XML définissant le contenu de chaque élément du [ListView],
  • le tableau des créneaux horaires du médecin,
  • la vue elle-même ;

Pour comprendre le reste de la vue, il nous faut d'abord expliquer le fonctionnement du [ListView] [1] ;

X-I-13. L'adaptateur [ListCreneauxAdapter]

Image non disponible

La classe [ListCreneauxAdapter] sert à définir une ligne du [ListView] :

Image non disponible

On voit ci-dessus, que selon le créneau a un rendez-vous ou non, l'affichage n'est pas le même. Le code de la classe [ListCreneauxAdapter] est le suivant :

 
CacherSélectionnez
  • ligne 5 : la classe [ListCreneauxAdapter] doit étendre un adaptateur prédéfini pour les [ListView], ici la classe [ArrayAdapter] qui comme son nom l'indique alimente le [ListView] avec un tableau d'objets, ici de type [CreneauMedecinJour]. Rappelons le code de cette entité :
 
CacherSélectionnez
  • la classe [CreneauMedecinJour] contient un créneau horaire (ligne 5) et un éventuel rendez-vous (ligne 6) ou null si pas de rendez-vous ;

Retour au code de la classe [ListCreneauxAdapter] :

  • ligne 17 : le constructeur reçoit quatre paramètres :
  • ligne 26 : le tableau des créneaux horaires est trié dans l'ordre croissant des horaires ;
  • l'activité Android en cours,
  • le fichier XML définissant le contenu de chaque élément du [ListView],
  • le tableau des créneaux horaires du médecin,
  • la vue elle-même ;

La méthode [getView] est chargée de générer la vue correspondant à une ligne du [ListView]. Celle-ci comprend trois éléments :

Image non disponible

Id

Type

Rôle

1

txtCreneau

TextView

créneau horaire

2

txtClient

TextView

le client

3

btnValider

TextView

lien pour ajouter / supprimer un rendez-vous

Le code de la méthode [getView] est le suivant :

 
CacherSélectionnez
  • ligne 1 : position est le n° de ligne qu'on va générer dans le [ListView]. C'est également le n° du créneau dans le tableau [creneauxMedecinJour]. On ignore les deux autres paramètres ;
  • ligne 4 : on récupère le créneau horaire à afficher dans la ligne du [ListView] ;
  • ligne 6 : la ligne est construite à partir de sa définition XML
Image non disponible

Le code de [creneau_medecin.xml] est le suivant :

 
CacherSélectionnez
Image non disponible
  • lignes 8-10 : le créneau horaire [1] est construit ;
  • lignes 12-20 : l'identité du client [2] est construite ;
  • ligne 23 : si le créneau n'a pas de rendez-vous ;
  • lignes 25-26 : on construit le lien [Ajouter] de couleur bleue ;
  • lignes 29-30 : sinon on construit le lien [Annuler] de couleur rouge ;
  • lignes 33-40 : quelque soit la nature lien [Ajouter / Supprimer] c'est la méthode [doValider] de la vue qui gèrera le clic sur le lien. La méthode recevra deux arguments :
  • ligne 42 : on rend la ligne qu'on vient de construire.
  • le n° du créneau qui a été cliqué,
  • le libellé du lien qui a été cliqué ;

On notera que c'est la méthode [doValider] de la vue qui gère les liens. On y vient.

X-I-14. La vue [AgendaVue] - 2

La méthode [doValider] est la suivante :

 
CacherSélectionnez
  • ligne 2 : on reçoit le n° du créneau dans lequel l'utilisateur a cliqué un lien ainsi que le libellé de ce lien ;
  • ligne 4 : le n° du créneau est mis en session ;
  • lignes 5-12 : on note des informations sur la position du créneau cliqué afin de pouvoir le réafficher ultérieurement à l'endroit où il était lorsqu'il a été cliqué. On note deux informations (firstPosition, top). Ces deux informations sont utilisées dans la méthode [onResume] :
 
CacherSélectionnez

L'explication du mécanisme mis en œuvre est assez complexe. Il faudrait faire un dessin. En ligne 6, on trouve l'URL qui donne cette explication ;

  • lignes 14-18 : selon le libellé du lien cliqué on ajoute (ligne 15) ou on supprime (ligne 17) un rendez-vous.

La méthode [doAjouter] est la suivante :

 
CacherSélectionnez
  • lignes 4-5 : on fait afficher la vue suivante [AjoutRvVue].

La méthode [doSupprimer] est la suivante :

 
CacherSélectionnez
  • lignes 5-7 : on lance une tâche asynchrone pour supprimer le rendez-vous en base. Parce qu'on a lancé une tâche asynchrone, il faut gérer ses notifications.

La méthode [notifyEvent] est la suivante :

 
CacherSélectionnez
  • lignes 7-9 : on gère la notification [WORK_INFO]. On se contente de mettre dans une liste les informations reçues. Ici, il n'y en aura qu'une, la chaîne " OK " ou une exception.

La méthode [notifyEndOfTasks] est appelée lorsque la tâche est terminée ;

 
CacherSélectionnez
  • ligne 6 : on récupère le résultat de la suppression, la chaîne " OK " si tout s'est bien passé, une exception sinon ;
  • ligne 8 : on teste le 1er cas ;
  • lignes 11-16 : si la suppression s'est bien passée, on lance une nouvelle tâche asynchrone pour régénérer l'agenda du médecin à partir de la base. On a pris soin auparavant de nettoyer la liste des résultats. La nouvelle tâche va s'exécuter et produire une information de type [AgendaMedecinJour]. Puis la méthode [notifyEndOfTasks] va être de nouveau appelée avec cette nouvelle information ;
  • ligne 22 : traite cette information ;
  • ligne 24 : elle est mise en session ;
  • lignes 26-31 : le [ListView] est régénéré à partir de ce nouvel agenda ;
  • lignes 32-34 : affichent une éventuelle exception issue de ces deux tâches.

X-I-15. La tâche [SupprimerRvTask]

Image non disponible

La tâche [SupprimerRvTask] supprime un rendez-vous de la façon suivante :

 
CacherSélectionnez

X-I-16. La vue [AjoutRvVue]

Image non disponible

C'est la vue suivante :

Image non disponible

Id

Type

Rôle

1

spinnerClients

Spinner

liste déroulante des clients

2

txtTitre2

TextView

une ligne d'information

3

btnValider

Button

bouton pour valider le rendez-vous

3

btnAnnuler

Button

bouton pour annuler la demande de validation

Le code de la vue est le suivant :

 
CacherSélectionnez
  • ligne 1 : la méthode [onResume] est appelée juste avant l'affichage de la vue. Elle va aller chercher en session le modèle qu'elle doit afficher ;
  • ligne 7 : la liste des clients est cherchée en session ;
  • lignes 8-19 : le contenu de la liste déroulante des clients est construit avec des lignes de la forme [Melle Brigitte Bistrou] ;
  • lignes 20-33 : la ligne d'information [2] est construite à partir d'informations trouvées également en session ;
  • ligne 35 : active les trois premiers liens du bandeau gauche. Permet à l'utilisateur de revenir sur les trois précédentes vues.

La méthode exécutée lors du clic sur le bouton [Valider] est la suivante :

 
CacherSélectionnez
  • lignes 5-10 : exécutent la tâche asynchrone [AjouterRvTask] qui va ajouter le rendez-vous en base.

Parce qu'elle a lancé une tâche asynchrone, la vue voit passer des notifications :

 
CacherSélectionnez
  • lignes 7-9 : on se contente d'emmagasiner les résultats (ici un seul) dans une liste.

La vue va être prévenue de la fin des tâches (une seule ici) :

 
CacherSélectionnez

La tâche [AjouterRvTask] rend une information de type [Rv], le rendez-vous ajouté. Ce cas est traité ligne 13 :

  • ligne 13 : résultat de la tâche [AjouterRvTask] ;
  • lignes 16-20 : on lance la tâche qui régénère l'agenda du médecin à partir des informations en base. Cette tâche va rendre une information de type [AgendaMedecinJour]. Ce cas est traité ligne 7 ;
  • ligne 7 : on a reçu l'agenda du médecin ;
  • ligne 9 : on le met dans la session pour la vue suivante ;
  • lignes 11-12 : on affiche la vue [AgendaVue] ;
  • lignes 25-27 : pour le cas où l'une des deux tâches précédentes renvoie une exception.

X-I-17. La tâche [AjouterRvTask]

Image non disponible

La tâche [AjouterRvTask] ajoute un rendez-vous de la façon suivante :

 
CacherSélectionnez

Ceci termine notre revue de l'exemple 7.

X-J. Conclusion de l'exemple 7

Rappelons l'architecture client / serveur de cet exemple :

Le serveur J2EE

Image non disponible

Le client Android

Image non disponible

Dans le document [pfm], une application web mobile avait été construite avec les mêmes fonctionnalités que celle qui vient d'être construite. Pour l'instant l'application web mobile l'emporte sur l'application native Android. En effet, elle fonctionne sur n'importe quel smartphone ce qui n'est pas le cas de notre application Android. Pour rendre celle-ci plus attractive, on pourrait utiliser des éléments du smartphone / tablette (caméra, sonnerie, micro…) que l'application web mobile ne pourrait utiliser. Mais ça peut relever du gadget pour un client de gestion par exemple. Plus intéressant, on peut déporter la couche [métier] du serveur sur le client Android. L'architecture devient alors la suivante :

Le serveur J2EE

Image non disponible

Le service REST expose désormais l'interface de la couche [DAO].

Le client Android

Image non disponible

La nouvelle couche [métier] du client Android va désormais avoir deux fonctionnalités :

  • s'interfacer avec le service REST de la couche [DAO] du serveur ;
  • embarquer la couche [métier] du serveur.

Le métier est désormais déporté sur les clients. On allège ainsi le serveur et on gagne probablement en performances. C'est plus difficile à atteindre avec une application web mobile.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Serge Tahé. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.