Revenons à l'architecture la plus générale des clients Android présentés jusqu'à maintenant, celle de l'exemple 2 :
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 :
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
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.
Enfin, on peut également obtenir une page d'erreurs :
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
Ecran de choix du médecin du rendez-vous
Ecran de choix du créneau horaire du rendez-vous
Ecran de choix du client du rendez-vous
Après la prise de rendez-vous :
X-B. L'architecture du projet▲
On aura une architecture client / serveur analogue à celle de l'exemple 2 de ce document :
Le serveur
- 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 :
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 :
X-C-1. La table [MEDECINS]▲
Elle contient des informations sur les médecins gérés par l'application [RdvMedecins].
- 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] :
- 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 :
- 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 :
- 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.
Avec [WampServer] (cf paragraphe , page ), on pourra procéder comme suit :
- 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],

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

- en [8], on importe un fichier SQL,
- qu'on désigne dans le système de fichiers avec le bouton [9],

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

- 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 :
- [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 :
Nous devons écrire la couche [REST] qui expose au monde web la couche [métier]. Celle-ci présente l'interface [IMetier] suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
package server.rdvmedecins.metier.service;
import java.util.Date;
import java.util.List;
import server.rdvmedecins.entities.jpa.Client;
import server.rdvmedecins.entities.jpa.Creneau;
import server.rdvmedecins.entities.jpa.Medecin;
import server.rdvmedecins.entities.jpa.Rv;
import server.rdvmedecins.entities.metier.AgendaMedecinJour;
public interface IMetier {
public List<Client> getAllClients();
public List<Medecin> getAllMedecins();
public List<Creneau> getAllCreneaux(Medecin medecin);
public List<Rv> getRvMedecinJour(Medecin medecin, Date jour);
public Client getClientById(Long id);
public Medecin getMedecinById(Long id);
public Rv getRvById(Long id);
public Creneau getCreneauById(Long id);
public Rv ajouterRv(Date jour, Creneau creneau, Client client);
public void supprimerRv(Rv rv);
public AgendaMedecinJour getAgendaMedecinJour(Medecin medecin, Date jour);
}
- 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]
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
public class RdvMedecinsException extends RuntimeException implements Serializable {
private static final long serialVersionUID = 1L;
private int code = 0;
public RdvMedecinsException() {
super();
}
public RdvMedecinsException(String message) {
super(message);
}
public RdvMedecinsException(String message, Throwable cause) {
super(message, cause);
}
public RdvMedecinsException(Throwable cause) {
super(cause);
}
public RdvMedecinsException(String message, int code) {
super(message);
setCode(code);
}
public RdvMedecinsException(Throwable cause, int code) {
super(cause);
setCode(code);
}
public RdvMedecinsException(String message, Throwable cause, int code) {
super(message, cause);
setCode(code);
}
...
}
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.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String titre;
private String nom;
private int version;
private String prenom;
....
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
La classe [Medecin] : représente un médecin
Sélectionnez 1.
2.
3.
4.
5.
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
...
}
La classe [Client] : représente un client
Sélectionnez 1.
2.
3.
4.
5.
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
...
}
La classe [Creneau] : représente un créneau horaire.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private int mdebut;
private int hfin;
private int hdebut;
private int mfin;
private int version;
private Medecin medecin;
...
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
La classe [Rv] : représente un rendez-vous.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Date jour;
private Creneau creneau;
private Client client;
...
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
La classe [AgendaMedecinJour] : l'agenda d'un médecin pour un jour donné.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
public class AgendaMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
private Medecin medecin;
private Date jour;
private CreneauMedecinJour[] creneauxMedecinJour;
...
public String toString() {
StringBuffer str = new StringBuffer("");
for (CreneauMedecinJour cr : creneauxMedecinJour) {
str.append(" ");
str.append(cr.toString());
}
return String.format("Agenda[%s,%s,%s]", medecin, new SimpleDateFormat("dd/MM/yyyy").format(jour), str.toString());
}
}
La classe [CreneauMedecinJour] : représente l'occupation d'un créneau horaire.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
public class CreneauMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
private Creneau creneau;
private Rv rv;
...
@Override
public String toString() {
return String.format("[%s %s]", creneau,rv);
}
}
X-F. Le serveur REST▲
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 :
- 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▲
Le fichier [pom.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>server.rdvmedecins</groupId>
<artifactId>server-rdvmedecins-rest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>server-rdvmedecins-rest</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<releaseCandidate>1</releaseCandidate>
<spring.version>3.1.1.RELEASE</spring.version>
<jackson.mapper.version>1.5.6</jackson.mapper.version>
</properties>
<repositories>
<repository>
<id>com.springsource.repository.bundles.release</id>
<name>SpringSource Enterprise Bundle Repository - Release</name>
<url>http://repository.springsource.com/maven/bundles/release</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.mapper.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>server.rdvmedecins</groupId>
<artifactId>server-rdvmedecins-metier</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
...
</plugins>
</build>
</project>
- 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.
X-F-3-a. Le fichier [web.xml]▲
Le fichier [web.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>REST for Medecins</display-name>
<servlet>
<servlet-name>restservices</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/rest-services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restservices</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
82.
83.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="dao" class="server.rdvmedecins.dao.DaoJpa" />
<bean id="metier" class="server.rdvmedecins.metier.service.Metier">
<property name="dao" ref="dao" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
</bean>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<context:component-scan base-package="server.rdvmedecins.rest" />
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="contentType" value="text/plain" />
</bean>
<bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<util:list id="beanList">
<ref bean="jsonMessageConverter" />
</util:list>
</property>
</bean>
</beans>
- 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]▲
La couche REST est implémentée par la classe [Rest] suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
82.
83.
84.
85.
86.
package server.rdvmedecins.rest;
...
@Controller
public class Rest {
@Autowired
private IMetier metier;
@Autowired
private View view;
@RequestMapping(value = "/getAllClients", method = RequestMethod.GET)
public ModelAndView getAllClients() {
...
}
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.GET)
public ModelAndView getAllMedecins() {
...
}
@RequestMapping(value = "/getAllCreneaux/{idMedecin}", method = RequestMethod.GET)
public ModelAndView getAllCreneaux(@PathVariable("idMedecin") long idMedecin) {
...
}
@RequestMapping(value = "/getRvMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public ModelAndView getRvMedecinJour(@PathVariable("idMedecin") long idMedecin, @PathVariable("jour") String jour) {
...
}
@RequestMapping(value = "/getClientById/{idClient}", method = RequestMethod.GET)
public ModelAndView getClientById(@PathVariable("idClient") long idClient) {
...
}
@RequestMapping(value = "/getMedecinById/{idMedecin}", method = RequestMethod.GET)
public ModelAndView getMedecinById(@PathVariable("idMedecin") long idMedecin) {
...
}
@RequestMapping(value = "/getRvById/{idRv}", method = RequestMethod.GET)
public ModelAndView getRvById(@PathVariable("idRv") long idRv) {
...
}
@RequestMapping(value = "/getCreneauById/{idCreneau}", method = RequestMethod.GET)
public ModelAndView getCreneauById(@PathVariable("idCreneau") long idCreneau) {
...
}
@RequestMapping(value = "/ajouterRv/{jour}/{idCreneau}/{idClient}", method = RequestMethod.GET)
public ModelAndView ajouterRv(@PathVariable("jour") String jour, @PathVariable("idCreneau") long idCreneau,
@PathVariable("idClient") long idClient) {
...
}
@RequestMapping(value = "/supprimerRv/{idRv}", method = RequestMethod.GET)
public ModelAndView supprimerRv(@PathVariable("idRv") long idRv) {
...
}
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public ModelAndView getAgendaMedecinJour(@PathVariable("idMedecin") long idMedecin, @PathVariable("jour") String jour) {
...
}
private ModelAndView createResponseError(String numero, String message) {
...
}
}
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
@RequestMapping(value = "/getAllClients", method = RequestMethod.GET)
public ModelAndView getAllClients() {
try {
List<Client> clients = metier.getAllClients();
return new ModelAndView(view, "data", clients);
} catch (Exception e) {
return createResponseError("103", e.getMessage());
}
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
private ModelAndView createResponseError(String numero, String message) {
Map<String, Object> modèle = new HashMap<String, Object>();
modèle.put("error", numero);
modèle.put("message", message);
return new ModelAndView(view, modèle);
}
Le client recevra une chaîne JSON de la forme :
Sélectionnez 1.
{"error":"54","message:"..."}
Voici un exemple d'exécution :
X-F-4-b. Liste des médecins▲
Le code est analogue :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.GET)
public ModelAndView getAllMedecins() {
try {
List<Medecin> medecins = metier.getAllMedecins();
return new ModelAndView(view, "data", medecins);
} catch (Exception e) {
return createResponseError("104", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-c. Liste des créneaux d'un médecin▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
@RequestMapping(value = "/getAllCreneaux/{idMedecin}", method = RequestMethod.GET)
public ModelAndView getAllCreneaux(@PathVariable("idMedecin") long idMedecin) {
try {
Medecin medecin = metier.getMedecinById(idMedecin);
List<Creneau> creneaux = metier.getAllCreneaux(medecin);
return new ModelAndView(view, "data", creneaux);
} catch (Exception e) {
return createResponseError("105", e.getMessage());
}
}
Voici un exemple d'exécution (vue partielle) :
X-F-4-d. Liste des rendez-vous d'un médecin▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
@RequestMapping(value = "/getRvMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public ModelAndView getRvMedecinJour(@PathVariable("idMedecin") long idMedecin, @PathVariable("jour") String jour) {
try {
Date jourRv = new SimpleDateFormat("dd-MM-yy").parse(jour);
Medecin medecin = metier.getMedecinById(idMedecin);
List<Rv> rv = metier.getRvMedecinJour(medecin, jourRv);
return new ModelAndView(view, "data", rv);
} catch (Exception e) {
return createResponseError("106", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-e. Obtenir un client identifié par son n°▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@RequestMapping(value = "/getClientById/{idClient}", method = RequestMethod.GET)
public ModelAndView getClientById(@PathVariable("idClient") long idClient) {
try {
return new ModelAndView(view, "data", metier.getClientById(idClient));
} catch (Exception e) {
return createResponseError("107", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-f. Obtenir un médecin identifié par son n°▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@RequestMapping(value = "/getMedecinById/{idMedecin}", method = RequestMethod.GET)
public ModelAndView getMedecinById(@PathVariable("idMedecin") long idMedecin) {
try {
return new ModelAndView(view, "data", metier.getMedecinById(idMedecin));
} catch (Exception e) {
return createResponseError("108", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-g. Obtenir un rendez-vous identifié par son n°▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@RequestMapping(value = "/getRvById/{idRv}", method = RequestMethod.GET)
public ModelAndView getRvById(@PathVariable("idRv") long idRv) {
try {
return new ModelAndView(view, "data", metier.getRvById(idRv));
} catch (Exception e) {
return createResponseError("109", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-h. Obtenir un créneau horaire identifié par son n°▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@RequestMapping(value = "/getCreneauById/{idCreneau}", method = RequestMethod.GET)
public ModelAndView getCreneauById(@PathVariable("idCreneau") long idCreneau) {
try {
return new ModelAndView(view, "data", metier.getCreneauById(idCreneau));
} catch (Exception e) {
return createResponseError("110", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-i. Ajouter un rendez-vous▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
@RequestMapping(value = "/ajouterRv/{jour}/{idCreneau}/{idClient}", method = RequestMethod.GET)
public ModelAndView ajouterRv(@PathVariable("jour") String jour, @PathVariable("idCreneau") long idCreneau,
@PathVariable("idClient") long idClient) {
try {
Date jourRv = new SimpleDateFormat("dd-MM-yyyy").parse(jour);
Client client = metier.getClientById(idClient);
Creneau creneau = metier.getCreneauById(idCreneau);
Rv rv = metier.ajouterRv(jourRv, creneau, client);
return new ModelAndView(view, "data", rv);
} catch (Exception e) {
return createResponseError("111", e.getMessage());
}
}
Voici un exemple d'exécution :
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
@RequestMapping(value = "/supprimerRv/{idRv}", method = RequestMethod.GET)
public ModelAndView supprimerRv(@PathVariable("idRv") long idRv) {
try {
Rv rv = metier.getRvById(idRv);
metier.supprimerRv(rv);
return new ModelAndView(view, "data", "OK");
} catch (Exception e) {
return createResponseError("112", e.getMessage());
}
}
Voici un exemple d'exécution :
X-F-4-k. Obtenir l'agenda d'un médecin▲
Le code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public ModelAndView getAgendaMedecinJour(@PathVariable("idMedecin") long idMedecin, @PathVariable("jour") String jour) {
try {
Date jourRv = new SimpleDateFormat("dd-MM-yyyy").parse(jour);
Medecin medecin = metier.getMedecinById(idMedecin);
AgendaMedecinJour agenda = metier.getAgendaMedecinJour(medecin, jourRv);
System.out.println(String.format("jourRv=%s, jour=%s",jourRv, agenda.getJour()));
return new ModelAndView(view, "data", agenda);
} catch (Exception e) {
return createResponseError("113", e.getMessage());
}
}
Voici un exemple d'exécution :
- 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▲
X-G-1. Le projet Eclipse▲
Le projet Eclipse de la couche [DAO] du client Android est le suivant :
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
package android.rdvmedecins.dao;
import java.util.Map;
public interface IDao {
public String executeRestService(String method, String urlService, Object request, Map<String, String> paramètres);
public void setTimeout(int millis);
}
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▲
X-H-1. Le projet Eclipse▲
Le projet Eclipse de la couche [métier] du client Android est le suivant :
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
package android.rdvmedecins.metier;
import java.util.List;
import android.rdvmedecins.dao.IDao;
import android.rdvmedecins.entities.dao.Client;
import android.rdvmedecins.entities.dao.Creneau;
import android.rdvmedecins.entities.dao.Medecin;
import android.rdvmedecins.entities.dao.Rv;
import android.rdvmedecins.entities.metier.AgendaMedecinJour;
public interface IMetier {
public abstract List<Client> getAllClients();
public abstract List<Medecin> getAllMedecins();
public abstract List<Creneau> getAllCreneaux(Long idMedecin);
public abstract List<Rv> getRvMedecinJour(Long idMedecin, String jour);
public abstract Client getClientById(Long idClient);
public abstract Medecin getMedecinById(Long idMedecin);
public abstract Rv getRvById(Long idRv);
public abstract Creneau getCreneauById(Long idCreneau);
public abstract Rv ajouterRv(String jour, Long idCreneau, Long idClient);
public abstract void supprimerRv(Long idRv);
public abstract AgendaMedecinJour getAgendaMedecinJour(Long idMedecin, String jour);
public abstract void setDao(IDao dao);
public abstract void setUrlServiceRest(String urlServiceRest);
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
package android.rdvmedecins.metier;
...
public class Metier implements IMetier {
private IDao dao;
private Gson gson = new Gson();
private String urlServiceRest;
public List<Client> getAllClients() {
String urlService = String.format("http://%s/getAllClients", urlServiceRest);
String réponse = dao.executeRestService("get", urlService, null, new HashMap<String, String>());
try {
return gson.fromJson(réponse, new TypeToken<List<Client>>() {
}.getType());
} catch (Exception ex) {
throw new RdvMedecinsException(String.format("Réponse incorrecte du serveur: %s", réponse), ex, 20);
}
}
public List<Medecin> getAllMedecins() {
...
}
...
...
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
public T méthode(T1 param1, T2 param2,...) {
String urlService = String.format("http://%s/unCheminAvecOuPasDeVariables", urlServiceRest);
Map<String, String> paramètres = new HashMap<String, String>();
paramètres.put("clé1", objet1);
paramètres.put("clé2", objet2);
String réponse = dao.executeRestService("get", urlService, null, paramètres);
try {
return gson.fromJson(réponse, new TypeToken<T>() {
}.getType());
} catch (Exception ex) {
throw new RdvMedecinsException(String.format("Réponse incorrecte du serveur: %s", réponse), ex, 20);
}
}
Prenons par exemple la méthode [getAllCreneaux]
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
public List<Creneau> getAllCreneaux(Long idMedecin) {
String urlService = String.format("http://%s/getAllCreneaux/{idMedecin}", urlServiceRest);
Map<String, String> paramètres = new HashMap<String, String>();
paramètres.put("idMedecin", String.valueOf(idMedecin));
String réponse = dao.executeRestService("get", urlService, null, paramètres);
try {
return gson.fromJson(réponse, new TypeToken<List<Creneau>>() {
}.getType());
} catch (Exception ex) {
throw new RdvMedecinsException(String.format("Réponse incorrecte du serveur: %s", réponse), ex, 22);
}
}
- 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.
X-I-1. Le projet Eclipse▲
Le projet Eclipse est le suivant :
- 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▲
La classe [MainActivity] est la suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
package android.rdvmedecins.ui.activity;
...
public class MainActivity extends FragmentActivity {
private Factory factory;
private Vue configVue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setProgressBarIndeterminateVisibility(false);
setContentView(R.layout.main);
factory = new Factory(this, new Config());
configVue = (Vue) factory.getObject(Factory.CONFIG_VUE, null, "ConfigVue");
showVue(configVue);
}
public void showVue(Vue vue) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.container, vue);
fragmentTransaction.commit();
}
}
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 :
X-I-3. La classe de configuration▲
La classe [Config] est la suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
package android.rdvmedecins.ui.activity;
public class Config {
private boolean verbose = true;
private int timeout=1000;
...
}
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é :
Le fichier [main.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_weight="0.1"
android:background="@color/lavenderblushh2" >
<TextView
android:id="@+id/textViewHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:text="@string/txt_header"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/blue" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_weight="0.8"
android:orientation="horizontal" >
<LinearLayout
android:id="@+id/left"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="@color/lightcyan2"
android:orientation="vertical" >
<TextView
android:id="@+id/lnk_Config"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:layout_marginTop="100dp"
android:gravity="center_vertical|center_horizontal"
android:text="@string/lnk_config"
android:textColor="@color/blue" />
<TextView
android:id="@+id/lnk_Accueil"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center_vertical|center_horizontal"
android:text="@string/lnk_accueil"
android:textColor="@color/blue" />
<TextView
android:id="@+id/lnk_Agenda"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center_vertical|center_horizontal"
android:text="@string/lnk_agenda"
android:textColor="@color/blue" />
<TextView
android:id="@+id/lnk_Ajout"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center_vertical|center_horizontal"
android:text="@string/lnk_ajout"
android:textColor="@color/blue" />
</LinearLayout>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:background="@color/floral_white"
tools:context=".MainActivity"
tools:ignore="MergeRootFrame" >
</FrameLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_weight="0.1"
android:background="@color/wheat1" >
<TextView
android:id="@+id/textViewBottom"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical|center_horizontal"
android:text="@string/txt_bottom"
android:textColor="@color/blue" />
</LinearLayout>
</LinearLayout>
et correspond à la vue suivante :
X-I-5. La vue [Config]▲
C'est la première vue affichée. Elle est composée des éléments suivants :
|
N°
|
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
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.
124.
125.
126.
127.
128.
129.
package android.rdvmedecins.ui.vues;
...
public class ConfigVue extends LocalVue {
private Button btnValider;
private Button btnAnnuler;
private EditText edtUrlServiceRest;
private TextView txtErrorUrlServiceRest;
private List<Object> results = new ArrayList<Object>();
private String urlServiceRest;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.config, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
...
btnAnnuler = (Button) activity.findViewById(R.id.btn_Annuler);
btnAnnuler.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
cancelAll();
cancelWaiting();
}
});
btnValider.setVisibility(View.VISIBLE);
btnAnnuler.setVisibility(View.INVISIBLE);
}
@Override
public void onResume() {
super.onResume();
activateLinks(0);
}
protected void doValider() {
...
ITask getAllMedecinsTask = (ITask) factory.getObject(Factory.GETALLMEDECINS_TASK, this, "GetAllMedecinsTask");
getAllMedecinsTask.doWork(edtUrlServiceRest.getText().toString());
ITask getAllClientsTask = (ITask) factory.getObject(Factory.GETALLCLIENTS_TASK, this, "GetAllClientsTask");
getAllClientsTask.doWork(edtUrlServiceRest.getText().toString());
beginWaiting();
beginMonitoring();
}
private boolean isPageValid() {
...
}
@Override
public void notifyEndOfTasks() {
cancelWaiting();
Toast.makeText(activity, "Travail terminé", Toast.LENGTH_LONG).show();
boolean erreur = false;
for (Object result : results) {
if (result instanceof Map<?, ?>) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) result;
String clé = map.keySet().iterator().next();
session.add(clé, map.get(clé));
} else if (result instanceof Exception) {
showException((Exception) result);
erreur = true;
break;
}
}
if (!erreur) {
Vue accueilVue = (Vue) factory.getObject(Factory.ACCUEIL_VUE, null, "AccueilVue");
mainActivity.showVue(accueilVue);
}
}
@Override
public void notifyEvent(IWorker worker, int eventType, Object event) {
super.notifyEvent(worker, eventType, event);
if (eventType == IBoss.WORK_INFO) {
results.add(event);
}
}
private void beginWaiting() {
...
}
protected void cancelWaiting() {
...
}
}
- 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]▲
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
package android.rdvmedecins.ui.vues;
...
public abstract class LocalVue extends Vue {
protected ISession session;
protected MainActivity mainActivity;
protected TextView lnkConfig;
protected TextView lnkAccueil;
protected TextView lnkAgenda;
protected TextView lnkAjout;
protected TextView[] links;
public LocalVue() {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
session = (ISession) factory.getObject(Factory.SESSION, (Object[]) null);
mainActivity = (MainActivity) activity;
lnkConfig = (TextView) activity.findViewById(R.id.lnk_Config);
lnkAccueil = (TextView) activity.findViewById(R.id.lnk_Accueil);
lnkAgenda = (TextView) activity.findViewById(R.id.lnk_Agenda);
lnkAjout = (TextView) activity.findViewById(R.id.lnk_Ajout);
links = new TextView[] { lnkConfig, lnkAccueil, lnkAgenda, lnkAjout };
}
protected void navigateToView(View v) {
...
}
protected void activateLinks(int idLink) {
...
}
protected void showException(Exception ex) {
DialogFragment dialog = new ExceptionVue(ex);
dialog.show(getFragmentManager(), "exception");
}
protected void showHourGlass() {
activity.setProgressBarIndeterminateVisibility(true);
}
protected void hideHourGlass() {
activity.setProgressBarIndeterminateVisibility(false);
}
@Override
abstract public void notifyEndOfTasks();
}
- 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 :

- ligne 51 : on utilise la classe [ExceptionVue] suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
package android.rdvmedecins.ui.vues;
...
public class ExceptionVue extends DialogFragment {
private Exception ex;
public ExceptionVue() {
}
public ExceptionVue(Exception ex) {
this.ex = ex;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String message = ex.getMessage();
Throwable cause = ex.getCause();
while (cause != null) {
message += String.format("\n[%s, %s]", cause.getClass().getCanonicalName(), cause.getMessage());
cause = cause.getCause();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.dialog_show_exception).setMessage(message)
.setPositiveButton(R.string.dialog_show_exception_close, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
return builder.create();
}
}
- 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] ;

- ligne 38 : on doit rendre la boîte de dialogue qui vient d'être créée.
Revenons au code de [showException] :
Sélectionnez 1.
2.
3.
4.
5.
6.
protected void showException(Exception ex) {
DialogFragment dialog = new ExceptionVue(ex);
dialog.show(getFragmentManager(), "exception");
}
- 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] :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
public abstract class LocalVue extends Vue {
protected ISession session;
protected MainActivity mainActivity;
protected TextView lnkConfig;
protected TextView lnkAccueil;
protected TextView lnkAgenda;
protected TextView lnkAjout;
protected TextView[] links;
public LocalVue() {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
session = (ISession) factory.getObject(Factory.SESSION, (Object[]) null);
mainActivity = (MainActivity) activity;
lnkConfig = (TextView) activity.findViewById(R.id.lnk_Config);
lnkAccueil = (TextView) activity.findViewById(R.id.lnk_Accueil);
lnkAgenda = (TextView) activity.findViewById(R.id.lnk_Agenda);
lnkAjout = (TextView) activity.findViewById(R.id.lnk_Ajout);
links = new TextView[] { lnkConfig, lnkAccueil, lnkAgenda, lnkAjout };
}
- 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 :

- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
protected void activateLinks(int idLink) {
int i = 0;
while (i < idLink) {
links[i].setTextColor(getResources().getColor(R.color.blue));
links[i].setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
navigateToView(v);
}
});
i++;
}
while (i < links.length) {
links[i].setTextColor(getResources().getColor(R.color.black));
links[i].setOnClickListener(null);
i++;
}
}
- 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 ;
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
protected void navigateToView(View v) {
String libellé = ((TextView) v).getText().toString();
if (libellé.equals(getResources().getString(R.string.lnk_config))) {
mainActivity.showVue((Vue) factory.getObject(Factory.CONFIG_VUE, (Object[]) null));
}
if (libellé.equals(getResources().getString(R.string.lnk_accueil))) {
mainActivity.showVue((Vue) factory.getObject(Factory.ACCUEIL_VUE, (Object[]) null));
}
if (libellé.equals(getResources().getString(R.string.lnk_agenda))) {
mainActivity.showVue((Vue) factory.getObject(Factory.AGENDA_VUE, (Object[]) null));
}
if (libellé.equals(getResources().getString(R.string.lnk_ajout))) {
mainActivity.showVue((Vue) factory.getObject(Factory.AJOUT_RV_VUE, (Object[]) null));
}
}
- 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▲
La fabrique crée tous les objets du client Android :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
82.
public class Factory implements IFactory {
public static final int CONFIG = -1;
public static final int SESSION = -2;
public static final int METIER = -3;
public static final int CONFIG_VUE = 10;
public static final int ACCUEIL_VUE = 11;
public static final int AGENDA_VUE = 12;
public static final int AJOUT_RV_VUE = 13;
public static final int GETALLMEDECINS_TASK = 30;
public static final int GETALLCLIENTS_TASK = 31;
public static final int GETAGENDAMEDECIN_TASK = 32;
public static final int AJOUTER_RV_TASK = 33;
public static final int SUPPRIMER_RV_TASK = 34;
private Config config;
private boolean verbose;
private int timeout;
private IMetier metier;
private ISession session;
private Vue configVue;
private Vue accueilVue;
private Vue agendaVue;
private Vue ajoutRvVue;
private Activity activity;
public Factory(Activity activity, Config config) {
this.activity = activity;
this.config = config;
verbose = config.isVerbose();
timeout = config.getTimeout();
}
@Override
public Object getObject(int id, Object... params) {
switch (id) {
case CONFIG:
return config;
case SESSION:
return getSession();
case METIER:
return getMetier(params);
case CONFIG_VUE:
return getConfigVue(params);
case ACCUEIL_VUE:
return getAccueilVue(params);
case AGENDA_VUE:
return getAgendaVue(params);
case AJOUT_RV_VUE:
return getAjoutRvVue(params);
case GETALLMEDECINS_TASK:
return getAllMedecinsTask(params);
case GETALLCLIENTS_TASK:
return getAllClientsTask(params);
case GETAGENDAMEDECIN_TASK:
return getAgendaMedecinTask(params);
case AJOUTER_RV_TASK:
return getAjouterRvTask(params);
case SUPPRIMER_RV_TASK:
return getSupprimerRvTask(params);
}
return null;
}
....
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]▲
Cette tâche fournit la liste des médecins :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
package android.rdvmedecins.ui.tasks;
...
public class GetAllClientsTask extends Task {
private Object info;
@Override
protected void onPreExecute() {
boss.notifyEvent(this, IBoss.WORK_STARTED, null);
}
@Override
protected Void doInBackground(Object... params) {
IMetier metier = (IMetier) factory.getObject(Factory.METIER, params[0]);
try {
List<Client> clients = metier.getAllClients();
Map<String,Object> result=new HashMap<String,Object>();
result.put("clients",clients);
info=result;
} catch (Exception ex) {
info = ex;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
boss.notifyEvent(this, IBoss.WORK_INFO, info);
boss.notifyEvent(this, IBoss.WORK_TERMINATED, null);
}
}
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]▲
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]▲
C'est la vue suivante :
|
N°
|
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
public void onResume() {
super.onResume();
medecins = (List<Medecin>) session.get("medecins");
String[] arrayMedecins = new String[medecins.size()];
int i = 0;
for (Medecin medecin : medecins) {
arrayMedecins[i] = String.format("%s %s %s", medecin.getTitre(), medecin.getPrenom(), medecin.getNom());
i++;
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item,
arrayMedecins);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerMedecins.setAdapter(dataAdapter);
activateLinks(1);
}
- 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.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
protected void doValider() {
if (!isPageValid()) {
return;
}
results.clear();
ITask getAgendaMedecinTask = (ITask) factory
.getObject(Factory.GETAGENDAMEDECIN_TASK, this, "AgendaMedecinJourTask");
getAgendaMedecinTask.doWork(idMedecin, jourRv);
beginWaiting();
beginMonitoring();
}
- 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.
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Override
public void notifyEvent(IWorker worker, int eventType, Object event) {
super.notifyEvent(worker, eventType, event);
if (eventType == IBoss.WORK_INFO) {
results.add(event);
}
}
- 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 ;
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
@Override
public void notifyEndOfTasks() {
cancelWaiting();
boolean erreur = false;
for (Object result : results) {
if (result instanceof AgendaMedecinJour) {
session.add("agenda", result);
} else if (result instanceof Exception) {
showException((Exception) result);
erreur = true;
break;
}
}
if (!erreur) {
Vue agendaVue = (Vue) factory.getObject(Factory.AGENDA_VUE, null, "AgendaVue");
mainActivity.showVue(agendaVue);
}
}
- 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]▲
La tâche rend l'agenda du médecin. Son code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
@Override
protected Void doInBackground(Object... params) {
IMetier metier = (IMetier) factory.getObject(Factory.METIER, (Object[])null);
try {
Long idMedecin = (Long) params[0];
String jourRv = (String) params[1];
AgendaMedecinJour agenda = metier.getAgendaMedecinJour(idMedecin, jourRv);
info = agenda;
} catch (Exception ex) {
info = ex;
}
return null;
}
- 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▲
C'est la vue suivante :
|
N°
|
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
public class AgendaVue extends LocalVue {
private TextView txtTitre2;
private ListView lstCreneaux;
private Button btnAnnuler;
private AgendaMedecinJour agenda;
private List<Object> results = new ArrayList<Object>();
private int firstPosition;
private int top;
...
@Override
public void onResume() {
super.onResume();
agenda = (AgendaMedecinJour) session.get("agenda");
Medecin medecin = agenda.getMedecin();
String text = String.format("Rendez-vous de %s %s %s le %s", medecin.getTitre(), medecin.getPrenom(),
medecin.getNom(), new SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE).format(agenda.getJour()));
txtTitre2.setText(text);
ArrayAdapter<CreneauMedecinJour> adapter = new ListCreneauxAdapter(mainActivity, R.layout.creneau_medecin,
agenda.getCreneauxMedecinJour(), this);
lstCreneaux.setAdapter(adapter);
lstCreneaux.setSelectionFromTop(firstPosition, top);
activateLinks(2);
btnAnnuler.setVisibility(View.INVISIBLE);
}
...
- 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]▲
La classe [ListCreneauxAdapter] sert à définir une ligne du [ListView] :
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package android.rdvmedecins.ui.vues;
...
public class ListCreneauxAdapter extends ArrayAdapter<CreneauMedecinJour> {
private CreneauMedecinJour[] creneauxMedecinJour;
private Context context;
private int layoutResourceId;
private AgendaVue vue;
public ListCreneauxAdapter(Context context, int layoutResourceId, CreneauMedecinJour[] creneauxMedecinJour,
AgendaVue vue) {
super(context, layoutResourceId, creneauxMedecinJour);
this.creneauxMedecinJour = creneauxMedecinJour;
this.context = context;
this.layoutResourceId = layoutResourceId;
this.vue = vue;
Arrays.sort(creneauxMedecinJour, new MyComparator());
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
...
}
class MyComparator implements Comparator<CreneauMedecinJour> {
...
}
}
- 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é :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
public class CreneauMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
private Creneau creneau;
private Rv rv;
...
}
- 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 :
|
N°
|
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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.
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
CreneauMedecinJour creneauMedecin = creneauxMedecinJour[position];
View row = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, parent, false);
TextView txtCreneau = (TextView) row.findViewById(R.id.txt_Creneau);
txtCreneau.setText(String.format("%02d:%02d-%02d:%02d", creneauMedecin.getCreneau().getHdebut(), creneauMedecin
.getCreneau().getMdebut(), creneauMedecin.getCreneau().getHfin(), creneauMedecin.getCreneau().getMfin()));
TextView txtClient = (TextView) row.findViewById(R.id.txt_Client);
String text;
if (creneauMedecin.getRv() != null) {
Client client = creneauMedecin.getRv().getClient();
text = String.format("%s %s %s", client.getTitre(), client.getPrenom(), client.getNom());
} else {
text = "";
}
txtClient.setText(text);
final TextView btnValider = (TextView) row.findViewById(R.id.btn_Valider);
if (creneauMedecin.getRv() == null) {
btnValider.setText(R.string.btn_ajouter);
btnValider.setTextColor(context.getResources().getColor(R.color.blue));
} else {
btnValider.setText(R.string.btn_supprimer);
btnValider.setTextColor(context.getResources().getColor(R.color.red));
}
btnValider.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
vue.doValider(position, btnValider.getText().toString());
}
});
return row;
}
- 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
Le code de [creneau_medecin.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheat" >
<TextView
android:id="@+id/txt_Creneau"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:text="@string/txt_dummy" />
<TextView
android:id="@+id/txt_Client"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_Creneau"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_Creneau"
android:text="@string/txt_dummy" />
<TextView
android:id="@+id/btn_Valider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_Client"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_Client"
android:text="@string/btn_valider"
android:textColor="@color/blue" />
</RelativeLayout>
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
protected void doValider(int position, String texte) {
session.add("position", position);
firstPosition = lstCreneaux.getFirstVisiblePosition();
View v = lstCreneaux.getChildAt(0);
top = (v == null) ? 0 : v.getTop();
if (texte.equals(getResources().getString(R.string.btn_ajouter))) {
doAjouter(position);
} else {
doSupprimer(position);
}
}
- 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] :
Sélectionnez 1.
2.
lstCreneaux.setSelectionFromTop(firstPosition, top);
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
private void doAjouter(int position) {
Vue ajoutRvVue = (Vue) factory.getObject(Factory.AJOUT_RV_VUE, null, "AjoutRvVue");
mainActivity.showVue(ajoutRvVue);
}
- lignes 4-5 : on fait afficher la vue suivante [AjoutRvVue].
La méthode [doSupprimer] est la suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
private void doSupprimer(int position) {
results.clear();
ITask supprimerRvTask = (ITask) factory.getObject(Factory.SUPPRIMER_RV_TASK, this, "SupprimerRvTask");
supprimerRvTask.doWork(agenda.getCreneauxMedecinJour()[position].getRv().getId());
beginWaiting();
beginMonitoring();
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Override
public void notifyEvent(IWorker worker, int eventType, Object event) {
super.notifyEvent(worker, eventType, event);
if (eventType == IBoss.WORK_INFO) {
results.add(event);
}
}
- 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 ;
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
@Override
public void notifyEndOfTasks() {
cancelWaiting();
Object result = results.get(0);
if (result instanceof String) {
results.clear();
ITask getAgendaMedecinTask = (ITask) factory.getObject(Factory.GETAGENDAMEDECIN_TASK, this,
"AgendaMedecinJourTask");
getAgendaMedecinTask.doWork(agenda.getMedecin().getId(),
new SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE).format(agenda.getJour()));
beginWaiting();
beginMonitoring();
} else {
if (result instanceof AgendaMedecinJour) {
session.add("agenda", result);
AgendaMedecinJour agenda = (AgendaMedecinJour) result;
ArrayAdapter<CreneauMedecinJour> adapter = new ListCreneauxAdapter(mainActivity, R.layout.creneau_medecin,
agenda.getCreneauxMedecinJour(), this);
lstCreneaux.setAdapter(adapter);
lstCreneaux.setSelectionFromTop(firstPosition, top);
} else if (result instanceof Exception) {
showException((Exception) result);
}
}
}
- 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]▲
La tâche [SupprimerRvTask] supprime un rendez-vous de la façon suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
@Override
protected Void doInBackground(Object... params) {
Long idRv = (Long) params[0];
IMetier metier = (IMetier) factory.getObject(Factory.METIER, (Object[]) null);
try {
metier.supprimerRv(idRv);
info = "OK";
} catch (Exception ex) {
info = ex;
}
return null;
}
X-I-16. La vue [AjoutRvVue]▲
C'est la vue suivante :
|
N°
|
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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
@SuppressWarnings("unchecked")
@Override
public void onResume() {
super.onResume();
clients = (List<Client>) session.get("clients");
String[] arrayClients = new String[clients.size()];
int i = 0;
for (Client client : clients) {
arrayClients[i] = String.format("%s %s %s", client.getTitre(), client.getPrenom(), client.getNom());
i++;
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item,
arrayClients);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerClients.setAdapter(dataAdapter);
int position = (Integer) session.get("position");
AgendaMedecinJour agenda = (AgendaMedecinJour) session.get("agenda");
medecin = agenda.getMedecin();
Creneau creneau = agenda.getCreneauxMedecinJour()[position].getCreneau();
idCreneau = creneau.getId();
jour = new SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE).format(agenda.getJour());
String titre2 = String.format(Locale.FRANCE,
"Prise de rendez-vous de %s %s %s le %s pour le créneau %02d:%02d-%02d:%02d", medecin.getTitre(),
medecin.getPrenom(), medecin.getNom(), jour, creneau.getHdebut(), creneau.getMdebut(), creneau.getHfin(),
creneau.getMfin());
txtTitre2.setText(titre2);
activateLinks(3);
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
protected void doValider() {
results.clear();
Long idClient = clients.get(spinnerClients.getSelectedItemPosition()).getId();
ITask ajouterRvTask = (ITask) factory.getObject(Factory.AJOUTER_RV_TASK, this, "AjouterRvTask");
ajouterRvTask.doWork(jour, idCreneau, idClient);
beginWaiting();
beginMonitoring();
}
- 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 :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Override
public void notifyEvent(IWorker worker, int eventType, Object event) {
super.notifyEvent(worker, eventType, event);
if (eventType == IBoss.WORK_INFO) {
results.add(event);
}
}
- 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) :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
@Override
public void notifyEndOfTasks() {
cancelWaiting();
Object result = results.get(0);
if (result instanceof AgendaMedecinJour) {
session.add("agenda", result);
Vue agendaVue = (Vue) factory.getObject(Factory.AGENDA_VUE, null, "AgendaVue");
mainActivity.showVue(agendaVue);
} else if (result instanceof Rv) {
results.clear();
ITask getAgendaMedecinTask = (ITask) factory.getObject(Factory.GETAGENDAMEDECIN_TASK, this,
"AgendaMedecinJourTask");
getAgendaMedecinTask.doWork(medecin.getId(), jour);
beginWaiting();
beginMonitoring();
} else if (result instanceof Exception) {
showException((Exception) result);
}
}
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]▲
La tâche [AjouterRvTask] ajoute un rendez-vous de la façon suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
@Override
protected Void doInBackground(Object... params) {
String jour = (String) params[0];
Long idCreneau = (Long) params[1];
Long idClient = (Long) params[2];
IMetier metier = (IMetier) factory.getObject(Factory.METIER, (Object[]) null);
try {
Rv rv = metier.ajouterRv(jour, idCreneau, idClient);
info = rv;
} catch (Exception ex) {
info = ex;
}
return null;
}
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
Le client Android
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
Le service REST expose désormais l'interface de la couche [DAO].
Le client Android
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.