cours n° 1 : Comprendre et développer le modèle, la vue et le présenteur d’une application GWT MVP Activity And Place

Table des matières

Introduction
1) Le rôle du Modèle, de la Vue et du Présenteur
2) Choix pédagogiques
2) Concevoir le Modèle
3) Concevoir la Vue
4) Concevoir le Présenteur


Introduction

Les composants MVP : le Modèle, la Vue et le Présenteur représentent les acteurs du pattern MVP (Model View Presenter) dont l’objectif est de proposer un modèle de conception pour développer les interfaces graphiques d’une application.

MVP Théorique

Dans le MVP, le Présenteur endosse le rôle du cerveau (comme le contrôleur dans le MVC), le modèle porte les informations métier et la vue représente l’interface graphique.

On compare souvent le pattern MVP au pattern MVC (Model View Controller), dont les points communs sont importants.
On peut relever quelques particularités du MVP qui le différencie du MVC. En premier lieu, la séparation stricte des responsabilités de la logique (dans le Présenteur) et de l’affichage (dans la vue). On peut noter également que l’interface de la vue indépendante de l’API graphique permet de mocker la vue dans les tests unitaires et que la vue (implémentation comme interface) peut dépendre ou non du modèle (ce qui n’est pas le cas dans le MVC ou la vue dépend systématiquement du modèle).

On peut considérer les composants MVP comme le cœur même de notre application. Ils contiennent le contenu métier et visuel de notre application mais ne suffisent pas à réaliser une application fonctionnelle en GWT MVP Activity And Place. Les composants de navigation et de localisation qui font l’objet du prochain cours combleront cette lacune.

Ce cours est organisé de la manière suivante. Dans un premier temps, nous étudierons le rôle de chacun des composants MVP à développer.
Nous expliquerons ensuite brièvement les choix pédagogiques pris dans le cadre de ce cours.
Enfin, nous apprendrons de manière théorique et pratique comment concevoir nos composants MVP.

1) Le rôle de chaque élément : le Modèle, la Vue et le Présenteur

a) Le Modèle

Il représente les informations que l’utilisateur souhaite visualiser et ou manipuler dans les vues de l’application.
C’est un acteur passif du modèle MVP. Il est manipulé par le Présenteur et également la vue si on s’inscrit dans le cadre du MVP Présenteur Superviseur (Le pattern MVP peut être implémenté de différentes façons, pour en savoir plus, consulter le cours MVP Presenter Superviseur ou MVP Vue Passive ?). Le modèle, lui, n’utilise ni l’un ni l’autre.

Quelques exemples de Modèle : les informations saisies dans une commande, les informations saisies dans un moteur de recherche, les informations affichées comme résultat d’une recherche.

b) La Vue

Elle affiche à l’écran les informations nécessaires au cas d’utilisation dont elle est liée et répond également aux actions de l’utilisateur.
Elle travaille en coopération avec le Présenteur. Dans son rôle actif, la Vue lui délègue les actions utilisateur qu’elle reçoit. Dans son rôle passif, la Vue se met à la disposition du Présenteur pour mettre à jour les informations qu’elle affiche, ainsi que pour récupérer ce que l’utilisateur a saisi.

Quelques exemples de Vue : un écran de connexion, un écran qui affiche des produits, un menu vertical dans le coin gauche de l’écran affichant des catégories de produit.

c) Le Présenteur

Contrairement au modèle et à la vue, le Présenteur n’est pas visible pour l’utilisateur de l’application. C’est un composant de contrôle qui permet d’orchestrer le fonctionnement de la couche web de notre application. Il a en commun avec le contrôleur du MVC d’être le cerveau des 3 éléments et d’être l’homme du milieu entre l’interface graphique et le modèle.

Il assure deux fonctions principales :
– Répondre aux sollicitations de la Vue qui lui délègue les événements utilisateurs qu’elle reçoit. Pour ce faire, le Présenteur peut lui-même réaliser les traitements ou bien collaborer avec des objets ayant davantage la connaissance pour réaliser ces traitements, comme par exemple invoquer un service RPC qui effectuerait des appels en base de données.
– S’assurer que la Vue affiche des informations conformes aux résultats des requêtes utilisateurs qui lui ont été transmises par la Vue. A cet effet, le Présenteur met à jour les informations de la Vue à partir des méthodes que l’interface de la Vue expose.

2) Choix pédagogiques

a) Implémentation MVP utilisée dans le cadre de ce cours

Au-delà du MVP Activity and Place de GWT, le pattern de conception MVP peut être implémentée avec quelques variantes. Dans le cadre de ces cours, nous utiliserons le MVP Presenter Superviseur, une conception MVP qui repose sur la connaissance du modèle par la vue. Pour connaître la différence entre les deux principales variantes du modèle MVP, je vous invite à lire le cours MVP Presenter Superviseur ou MVP Vue Passive ?

MVP Supervising Presenter

Diagramme de classe des composants MVP (Supervising Presenter)

b) Distinction entre Présenteur et Activité

Au niveau de la représentation de leur implémentation, le Présenteur et l’activité ne forme qu’une seule et même classe Java.
Conceptuellement, la notion d’activité diffère néanmoins de celle de Présenteur.
Le Présenteur est le P du pattern MVP alors que l’activité quant à elle représente en quelque sorte une surcouche appliquée au Présenteur lui permettant, au prix de certes certaines contraintes, de bénéficier des fonctionnalités de la gestion de la navigation, prêts à l’emploi ou à faibles coûts de développement.
Cette différence conceptuelle se traduit également sur le plan technique. En effet, chaque classe d’implémentation du Présenteur/Activité dérive à la fois de l’interface Activity du framework GWT et d’une interface Presenter qui est à définir selon les besoins des cas d’utilisations associés à notre Présenteur/Activité.

Néanmoins, pour des raisons de clarté nous nous efforcerons de séparer l’étude qui concerne l’aspect Présenteur de celle qui concerne l’aspect Activité.
Dans ce cours, nous traiterons de l’aspect Présenteur. On se concentra donc sur le pattern MVP. L’aspect activité sera, lui, passé au crible dans le cours suivant « le développement des composants de navigation et de localisation ».


Comme le montre le schéma, à l’exception de l’interface isWidget, les composants MVP que nous développons ne sont pas contraints d’utiliser des classes ou des interfaces spécifiques fournies au MVP par le framework.
Différentes règles de conception sont néanmoins à suivre si on souhaite implémenter le pattern MVP correctement.

Pour le Modèle, la Vue et le Présenteur, nous présenterons :
– leur représentation Java
– la manière de les implémenter
– la manière de les instancier lorsque cela est pertinent
– un exemple complet du composant MVP illustrant les concepts exposés

1) Concevoir le modèle

a) La représentation Java du modèle

Le modèle peut être représenté par un ou plusieurs types simples fournis par le JDK.
Nous pouvons aussi créer nos propres classes pour représenter les informations qui composent le modèle.

b) Créer le modèle

Un modèle très simple pourra être représenté par un type simple comme une chaîne de caractères, une date, etc… Par exemple, la vue d’un moteur de recherche simple utilisera une chaîne de caractères pour représenter son modèle :

String search

Un modèle plus riche en information élémentaire devra lui être représenté par un ensemble de types simples, agrégés ou non dans une structure. Par exemple, une vue de connexion à son compte pourrait manipuler deux informations : un nom d’utilisateur et un mot de passe. Le modèle pourrait être représenté soit par l’union de deux chaînes de caractères :

String username, String password

, soit par une classe structurant ces informations :

public class SignInRequest implements Serializable{
    private String username ;
    private String password ;
}

Une vue d’enregistrement de compte qui porte davantage d’informations aurait, elle, tout intérêt à utiliser une classe agrégeant les informations véhiculées, plutôt que de les transmettre unitairement :

public class User implements Serializable {
 
    private String username;
 
    private String lastName;
 
    private String firstName;
 
    private String email;
 	……
 
}

Ces exemples montrent que si la manière de représenter son modèle n’est pas figée, utiliser une représentation adaptée améliore cependant grandement la conception et le développement de nos composants MVP.

A partir du moment où nous utilisons nos propres classes pour représenter le modèle, proposer des accesseurs et des modificateurs pour les champs privés de notre classe que notre Présenteur et notre vue souhaitent manipuler, s’avère très souvent indispensable.

c) Exemple de Modèle représentant l’enregistrement d’un compte utilisateur

public class User implements Serializable {
 
    private String username;
 
    private String lastName;
 
    private String firstName;
 
    private String email;
 
    private String password;
 
    private String passwordAgain;
 
  public String getUsername() {
	return username;
    }
 
    public void setUsername(String username) {
	this.username = username;
    }
 
    public String getLastName() {
	return lastName;
    }
 
    public void setLastName(String lastName) {
	this.lastName = lastName;
    }
 
    public String getFirstName() {
	return firstName;
    }
 
    public void setFirstName(String firstName) {
	this.firstName = firstName;
    }
 
    public String getEmail() {
	return email;
    }
 
    public void setEmail(String email) {
	this.email = email;
    }
 
    public String getPassword() {
	return password;
    }
 
    public void setPassword(String password) {
	this.password = password;
    }
 
    public String getPasswordAgain() {
	return passwordAgain;
    }
 
    public void setPasswordAgain(String passwordAgain) {
	this.passwordAgain = passwordAgain;
    }
}

2)Concevoir la Vue

a) La représentation Java de la Vue

Au niveau du code, la Vue prend la forme d’une interface et d’une implémentation Java que nous devons développer selon les besoins fonctionnels et ergonomiques de notre application.

Son implémentation est constituée de différents widgets fournis par GWT : champs de saisie simples, boutons radio, labels, etc… et aussi de nos propres widgets si nous en rencontrons le besoin.

b) Créer l’interface de la Vue

1. Etendre l’interface com.google.gwt.user.client.ui.IsWidget

Exemple :

public interface SignInView extends IsWidget {}

D’après la JavaDoc, un des objectifs de l’interface IsWidget est de pouvoir mocker facilement un composant graphique dans les tests unitaires Java JRE (ceux qui n’étendent pas GwtTestCase).
Sur le plan de la compilation, cet héritage est inévitable pour que notre Vue puisse être compatible avec les mécanismes de navigation fournis par le framework.
Lorsqu’une activité est sur le point de démarrer, le gestionnaire d’activité manipule un objet dont le rôle est de contenir la Vue associée à l’activité en cours de fonctionnement.
Cet objet est alors mis à la disposition de l’activité lors de son démarrage (méthode start() de l’activité) afin de lui permettre d’afficher à l’écran, la Vue à laquelle elle est associée.
Cet objet conteneur de notre Vue doit être une instance de l’interface AcceptsOneWidget. Interface présentant une seule méthode pour contenir un widget et ce widget doit justement être de type IsWidget :

package com.google.gwt.user.client.ui;
/**
 * Implemented by displays that can be given accept an {@link IsWidget}
 * to show.
 */
public interface AcceptsOneWidget {
  /**
   * Set the only widget of the receiver, replacing the previous
   * widget if there was one.
   *
   * @param w the widget, or <code>null</code> to remove the widget
   *
   * @see SimplePanel
   */
  void setWidget(IsWidget w);
}

Cecla explique la nécessité pour les interfaces de nos Vues d’étendre l’interface IsWidget.

2. Doter l’interface de la Vue de méthodes d’affichage de haut niveau pour permettre la communication du Présenteur vers la Vue

Il s’agit de méthodes de mises à jour des informations affichées sur la Vue, et de méthodes permettant la récupération des informations affichées sur cette dernière. Une Vue de connexion pourrait avoir les méthodes suivantes dans son interface :

 
public interface SignInView extends IsWidget {
 
    /**
     * Allow to do a reset on the sign in form fields : username and password
     */
    void reset();
 
    /**
     * Display an error message if the connection fails
     * 
     * @param messageErreur
     */
    void displayErrorMsg(String messageErreur);
. . .
}

Ces méthodes permettent au Présenteur d’interagir avec la Vue de manière contractualisée et indépendante de l’implémentation graphique sous-jacente contenue dans la Vue.
En réalité, les méthodes à proposer dans notre interface dépendent également du modèle de conception MVP que nous choisissons.
Comme nous l’avons évoqué, Il existe différentes manières d’implémenter le MVP. Nous discutons de ce sujet important dans le cours « MVP Presenter Superviseur ou MVP Vue Passive ».

3. Fournir à l’interface de notre Vue une méthode d’enregistrement du Présenteur

Une méthode générique est nécessaire à l’interface de chacune de nos Vues.
Pour être en la mesure d’accomplir son rôle de délégation que nous allons étudier dans la gestion des évènements utilisateurs, l’implémentation de la Vue doit avoir une référence vers une instance de l’interface du Présenteur. Réciproquement, l’implémentation du Présenteur a besoin d’avoir une référence vers une instance de l’interface de la Vue pour pouvoir garder à jour les informations de la Vue (sujet que nous avons abordé dans le point 2. Doter l’interface de la Vue de méthodes d’affichage de haut niveau pour permettre la communication du Présenteur vers la Vue).

dependance-bidirectionnelle-presenteur-vue

Cette association bidirectionnelle entre le Présenteur et la Vue s’effectue au démarrage de l’activité à partir de la méthode start(…) :

package com.google.gwt.activity.shared;
 
public interface Activity {
. . .
void start(AcceptsOneWidget panel, EventBus eventBus);
. . .
}

qui rappelons-le ne fait qu’un avec l’implémentation du Présenteur.
C’est au développeur de l’application de réaliser cette double association.
Illustrons nos propos avec un exemple concret : le cas d’utilisation de connexion à son compte. Nous nous reposerons sur les composants suivants :
SignInView (interface de la Vue), SignInView.Presenter (interface du présenteur) et SignInActivity (implémentation du présenteur/activité).

L’association est assez simple à réaliser du côté de l’activité puisque c’est elle qui charge la Vue lors de son démarrage :

public class SignInActivity implements SignInView.Presenter, Activity{
. . .
private SignInView signInView;
. . .
 
     /**
     * Invoked by the ActivityManager to start a new Activity
     */
    @Override
    public void start(AcceptsOneWidget containerWidget, EventBus eventBus){
      // Associate the view to the presenter
	signInView = clientFactory.getSignInView();
	. . .
    }
. . .
}

Du côté de la Vue, l’association est moins évidente étant donné que la Vue joue un rôle passif dans le démarrage de l’activité. Pour pouvoir réaliser cette autre association, nous devons donc ajouter à l’interface de notre Vue une méthode prenant en paramètre une instance du type de l’interface du Présenteur, dont le but est d’enregistrer le Présenteur auprès de la Vue.

public interface SignInView extends IsWidget {
. . .
setPresenter(SignInView.Presenter signInPresenter)
. . .
}

La méthode start() peut ainsi initialiser l’association bidirectionnelle entre le Présenteur et la Vue :

public class SignInActivity implements SignInView.Presenter, Activity {
  /**
     * Invoked by the ActivityManager to start a new Activity
     */
    @Override
    public void start(AcceptsOneWidget containerWidget, EventBus eventBus){
      // Associate the view to the presenter
	signInView = clientFactory.getSignInView();
 
      // Associate the presenter to the view 
	signInView.setPresenter(this);
    }

c) Créer l’implémentation de la Vue

Elle est composée de 3 éléments :
– Les composants graphiques
– La gestion des évènements utilisateurs
– L’implémentation de l’interface de la Vue

1. Les composants graphiques : UiBinder ou programmation Java ?

Il n’y a aucune obligation à procéder d’une manière ou d’une autre.
Personnellement, que ce soit pour une Vue du pattern MVP ou non ou bien un composant GWT réutilisable ou non, j’utilise systématiquement l’UIBinder pour avoir une conception graphique plus naturelle. Je pense qu’une Vue basée sur un système de template permet d’aligner davantage le code de la Vue au rendu visuel attendu.
J’utiliserais donc sans aucune modération l’UIBinder dans mes illustrations.

2. La gestion des événements utilisateurs

Selon le cas d’utilisation, l’utilisateur de l’application peut entrer ou non en interaction avec la Vue. Dans une application raisonnablement interactive, c’est le cas pour la majorité des écrans, si ce n’est tous les écrans.
Cette interaction peut avoir lieu lors de la saisie d’un champ, de la validation d’un formulaire, d’un click sur un bouton ou lien, etc…
Un des rôles intangibles de la Vue dans une application GWT, que l’on fasse du MVP Activity And Place ou non, est la capture des évènements utilisateurs que l’on souhaite traiter.

Par exemple, dans le cas d’utilisation de création d’un compte utilisateur, lorsque l’utilisateur clique sur le bouton de validation du formulaire, nous souhaitons que la Vue intercepte l’évènement :
ecran-enregistrement-compte

public class RegisterViewImpl extends Composite implements RegisterView{
. . .
   @UiHandler("registerBtn")
    public void register(ClickEvent event) {
	// Handle user request
. . .
    } 
. . .
}

Dans le MVP Présenteur Superviseur, le traitement d’un évènement se fait toujours en une ou deux étapes successives : la reconstitution du Modèle (étape optionnelle) et la délégation du traitement au Présenteur (passage obligé).
Dans tous les cas, la vue ne doit pas réaliser de logique métier dans la capture des évènements.

 

  • La reconstitution du Modèle :

 

Elle est réalisée à partir des informations contenues dans les widgets.
Cette étape n’est pas un passage obligé. Son utilité dépend essentiellement du nombre d’informations à traiter dans la requête utilisateur. Par exemple, dans le cas d’utilisation de création d’un compte utilisateur, la reconstitution du Modèle est pertinente car elle nous permet d’encapsuler toutes les informations du compte dans un seul et même objet :

public class RegisterViewImpl extends Composite implements RegisterView{
 
    interface MyUiBinder extends UiBinder<HTMLPanel, RegisterViewImpl> {
    }
 
    private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
 
    @UiField
    TextBox usernameTextBox;
 
    @UiField
    TextBox lastNameTextBox;
 
    @UiField
    TextBox firstNameTextBox;
 
    @UiField
    TextBox emailTextBox;
 
    @UiField
    TextBox passwordTextBox;
 
    @UiField
    TextBox passwordAgainTextBox;
 
    private RegisterPresenter presenter;
 
    public CreationCompteViewImpl() {
        initWidget(uiBinder.createAndBindUi(this));
    }
 
   @UiHandler("registerBtn")
    public void register(ClickEvent event) {	
        User user = buildUserFromUI();
. . .
    }
 
    private User buildUserFromUI() {
        User user = new User ();
        user.setUsername(usernameTextBox.getText());
        user.setEmail(emailTextBox.getText());
        user.setLastName(lastNameTextBox.getText());
	user.setFirstName(firstNameTextBox.getText());
	user.setPassword(passwordTextBox.getText());
	user.setPasswordAgain(passwordAgainTextBox.getText());
 
        return user;
    }
. . .
}

La reconstitution du Modèle, puis son utilisation permet d’obtenir un code pour la communication d’information entre la Vue et le Présenteur plus simple à développer, lire, maintenir, etc… que si nous transmettions autant de paramètres au Présenteur que d’informations élémentaires qui constituent le compte utilisateur. C’est un problème que l’on rencontre assez souvent en Java et plus généralement en programmation, lorsqu’on est tenté d’utiliser un constructeur ou une méthode comportant un nombre important d’arguments (au moins 4 ou 5) dont plusieurs ont le même type ou reposent sur des types compatibles. Dans ce genre de cas, on augmente les chances de se tromper dans l’ordre des arguments effectifs lors de l’appel. Les types étant compatibles, la compilation passe. Au mieux, l’erreur est détectée à l’exécution si elle engendre la levée d’une exception ou une incohérence fonctionnelle notable. Au pire, l’erreur reste sous silence un long moment avant d’être détectée.

A contrario, dans le cas d’utilisation de connexion à son compte, il y a nettement moins d’intérêt à ce que la Vue alimente un objet Modèle. La Vue peut déléguer au Présenteur l’événement de connexion en transmettant littéralement les informations username et password sans risquer d’obscurcir la communication des informations et de générer des erreurs de développement.

 

  • La délégation du traitement au Présenteur :

 

 

La Vue sollicite la méthode de l’interface du Présenteur ayant pour fonction le traitement de l’évènement utilisateur. On constate la symétrie entre les évènements utilisateurs interceptés par la vue dans son implémentation et l’interface du Présenteur : chaque méthode de l’interface du Présenteur existe parce que l’implémentation de la Vue en a besoin pour lui déléguer l’action utilisateur que notre application juge digne d’intérêt.
Lorsque la Vue délègue au Présenteur le traitement, selon le contexte informationnel, elle peut lui transmettre aucun, un ou plusieurs paramètres.

Par exemple, dans le cas d’utilisation de connexion à son compte, la Vue peut transmettre au Présenteur deux chaînes de caractères : le nom d’utilisateur et le mot de passe :

public class SignInViewImpl extends Composite implements SignInView {
. . .
@UiHandler("signInBtn")
    public void signIn(ClickEvent event) {
        presenter.signIn(usernameTextBox.getText(), passwordTextBox.getText());
    }
. . .
}

Dans le cas de l’enregistrement d’un compte, la Vue peut transmettre au Présenteur une instance du Modèle représentant un utilisateur :

public class RegisterViewImpl extends Composite implements RegisterView {
. . .
@UiHandler("registerBtn")
public void register(ClickEvent event) {
	User user = buildUserFromUI();
	presenter.register(user );
     }
. . .
}

Dans le cas de la clôture d’un compte, le Présenteur n’a besoin d’aucun paramètre pour traiter la demande puisque l’utilisateur est déjà connecté à l’application et que par conséquent, grâce au contexte informationnel, nous sommes capable de déterminer qui est l’utilisateur souhaitant clôturer son compte:

public class UnregisterViewImpl extends Composite implements UnregisterView {
. . .
@UiHandler("unregisterBtn")
    public void unregister(ClickEvent event) {
        presenter.unregister();
    }
. . .
}

3. Implémenter le contrat d’interface de la Vue

L’implémentation de la méthode d’enregistrement du Présenteur auprès de la Vue est très simple :

public class AnyViewImpl extends Composite implements AnyView {
. . .
@Override
    public void setPresenter(Presenter presenter) {
        this.presenter = presenter;
    }
. . .
}

Pour rappel, les autres méthodes du contrat d’interface de la Vue ont pour rôle de mettre à jour l’état visuel de la Vue. Pour rappel, ces méthodes sont appelés par le Présenteur.
La Vue ne doit cependant pas réaliser de logique métier au sein de ses méthodes, elle doit rester cantonner à des opérations de data binding.

Certaines méthodes n’ont pas besoin d’avoir de paramètre, le nom de la méthode suffit. Par exemple, c’est le cas des méthodes de réinitialisation des formulaires de saisie de la Vue :

Pour un formulaire de connexion, on réinitialiserait la Vue de cette manière :

public class SignInViewImpl extends Composite implements SignInView {
. . .
@Override
    public void reset() {
        passwordTextBox.setText("");
        usernameTextBox.setText("");
    }
. . .
}

D’autres méthodes sont paramétrées. Dans ce cas, l’implémentation de la Vue peut réaliser davantage de traitements mais pas nécessairement, tout dépend de « l’intelligence » des composants widgets que nous manipulons derrière.
Une méthode de la Vue qui aurait comme fonction d’afficher une liste de catégories de produits pourrait dévoiler un code aussi court que simple, la logique de data binding étant déjà implémentée dans la configuration de notre widget de type CellTable :

public class ProductCategoriesViewImpl extends Composite implements ProductCategoriesView{
. . .
 
@UiField
CellTable<CategorieProduit> productCategoriesCellTable;
. . .
@Override
  public void displayProductCategories(List<ProductCategory> productCategories) {
        productCategoriesCellTable.setRowData(productCategories);
    }
. . .
}

La méthode d’affichage d’un profil utilisateur d’une Vue qui repose sur des widgets d’affichage de texte plus basiques en matière de data binding nécessiterait davantage de code à écrire, mais de nature toujours simple et mécanique. Répétons-le, les méthodes d’implémentation de l’interface de la Vue doivent rester exclusivement des opérations de data binding :

public class DisplayUserProfileViewImpl extends Composite implements DisplayUserProfileView {
. . .
@Override
    public void displayUserProfile(User user) {
        lastNameTextBox.setText(user.getLastName());
        firstNameTextBox.setText(user.getFirstName());
        usernameTextBox.setText(user.getUsername());	
	......
    }
. . .
}

f) Instancier la Vue

Il n’existe pas une manière exclusive d’instancier la Vue. Il est par contre indispensable qu’elle soit accessible à notre activité (ou Présenteur) puisqu’elle en a besoin.
Une factory (avec des méthodes statiques ou non) pour récupérer la Vue souhaitée est une solution acceptable :

public class ClientFactory
{
    private static final SignInView signInView = new SignInViewImpl();
 
    public SignInView getSignInView() {
        return signInView ;
    }
	...
}

La méthode pour récupérer une Vue de connexion retourne une instance de type de SignInView afin de respecter la règle de communication du Presenter vers la Vue qui doit se faire par l’interface de la Vue : SignInView, et non son implémentation. L’utilisation d’une factory répond donc parfaitement à ce besoin.

On remarque que la méthode de récupération de la Vue renvoie toujours la même instance de Vue, comme il est préconisé pour des raisons de performances, la création d’une Vue sous GWT étant assez gourmande en terme de traitements.

g) Exemple : une Vue de création de compte utilisateur

L’interface de la Vue devra présenter toutes les méthodes nécessaires au Présenteur de création de compte utilisateur.
On peut noter que l’interface de la vue contient la déclaration de l’interface du Présenter. Cette manière de procéder est réaliser pour des raisons pratiques, nous en discuterons dans le point suivant qui traite du Présenteur.

package david.haccoun.teaching.gwt.client.core.register.view;
 
import com.google.gwt.user.client.ui.IsWidget;
 
import david.haccoun.teaching.gwt.client.core.common.model.User;
 
/**
 * Interface for the Register View
 */
public interface RegisterView extends IsWidget {
 
    /**
     * Allow to do a reset on the Register User form fields
     */
    void reset();
 
    /**
     * 
     * Display an error message if the registration fails
     * 
     * @param errorMsg
     */
    void displayErrorMsg(String errorMsg);
 
    /**
     * Display a success message if the registration succeeds
     * 
     * @param messageErreur
     */
    void displaySuccessMsg(String successMsg);
 
    /**
     * Associate the RegisterPresenter with the RegisterView
     * 
     * @param presenter
     */
    void setPresenter(RegisterPresenter presenter);
 
    /**
     * Interface for the Register Presenter
     */
    public interface RegisterPresenter {
	/**
	 * @param user
	 */
	void register(User user);
    }
 
}

Si nous utilisons l’UIBinder pour concevoir notre Vue, nous pourrions avoir comme fichier XML de déclaration :

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
	xmlns:v='urn:import:com.google.gwt.user.cellview.client' xmlns:c='urn:import:davidhxxx'>
 
	<g:HTMLPanel>
		<h2>Registration</h2>
 
		<g:VerticalPanel horizontalAlignment="ALIGN_CENTER" styleName="cadreCenter">
			<g:Label>Username :</g:Label>
			<g:TextBox ui:field="usernameTextBox" width="150px" />
			<g:Label>Last Name :</g:Label>
			<g:TextBox ui:field="lastNameTextBox" width="200px" />
			<g:Label>First Name</g:Label>
			<g:TextBox ui:field="firstNameTextBox" width="200px" />
			<g:Label>email :</g:Label>
			<g:TextBox ui:field="emailTextBox" width="200px" />
			<g:Label>Password :</g:Label>
			<g:PasswordTextBox ui:field="passwordTextBox" width="150px" />
			<g:Label>Password Again:</g:Label>
			<g:PasswordTextBox ui:field="passwordAgainTextBox" width="150px" />
 
			<g:Button ui:field="registerBtn" styleName="validationButton" width="80px">Register</g:Button>
		</g:VerticalPanel>
 
	</g:HTMLPanel>
</ui:UiBinder>

L’implémentation de notre Vue devra remplir le contrat de l’interface de la Vue mais également répondre aux évènements utilisateurs pour les déléguer au Présenteur :

package david.haccoun.teaching.gwt.client.core.register.view;
 
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.TextBox;
 
import david.haccoun.teaching.gwt.client.core.common.model.User;
 
public class RegisterViewImpl extends Composite implements RegisterView {
 
    interface MyUiBinder extends UiBinder<HTMLPanel, RegisterViewImpl> {
    }
 
    private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
 
    @UiField
    TextBox usernameTextBox;
 
    @UiField
    TextBox lastNameTextBox;
 
    @UiField
    TextBox firstNameTextBox;
 
    @UiField
    TextBox emailTextBox;
 
    @UiField
    TextBox passwordTextBox;
 
    @UiField
    TextBox passwordAgainTextBox;
 
    private RegisterPresenter presenter;
 
    public RegisterViewImpl() {
	initWidget(uiBinder.createAndBindUi(this));
    }
 
    @UiHandler("registerBtn")
    public void register(ClickEvent event) {
	User user = buildUserFromUI();
	presenter.register(user);
    }
 
    private User buildUserFromUI() {
	User user = new User();
	user.setUsername(usernameTextBox.getText());
	user.setEmail(emailTextBox.getText());
	user.setLastName(lastNameTextBox.getText());
	user.setFirstName(firstNameTextBox.getText());
	user.setPassword(passwordTextBox.getText());
	user.setPasswordAgain(passwordAgainTextBox.getText());
 
	return user;
    }
 
    @Override
    public void reset() {
	usernameTextBox.setText("");
	passwordTextBox.setText("");
	passwordAgainTextBox.setText("");
	lastNameTextBox.setText("");
	firstNameTextBox.setText("");
	emailTextBox.setText("");
    }
 
    @Override
    public void displayErrorMsg(String messageErreur) {
	Window.alert(messageErreur);
    }
 
    @Override
    public void setPresenter(RegisterPresenter presenter) {
	this.presenter = presenter;
    }
 
    @Override
    public void displaySuccessMsg(String messsageSucces) {
	Window.alert(messsageSucces);
 
    }
 
}

3) Concevoir le Présenteur

a) La représentation Java du Présenteur

À l’instar de la Vue, le Présenteur est composé d’une interface et de son implémentation.
Le Présenteur ne doit pas contenir de composants graphiques ou réaliser de traitements sur l’interface graphique.

b) Créer un Présenteur

Attention : Dans les conventions de nommage de GWT MVP Activity And Place, l’implémentation commune à l’interface du Présenteur et à celle de l’activité donne à l’aspect activité davantage de visibilité. Par exemple, le nom des classes d’implémentation est suffixé par Activity (exemple : SignInActivity). Ce choix peut s’expliquer par le fait que les composants de navigation qui font « vivre » l’implémentation de nos Présenteurs/Activités manipulent des objets de type com.google.gwt.activity.shared.Activity. L’étude de l’activité et en particulier de son cycle de vie nous permettra de donner du sens à ces propros.

1. Créer une interface pour notre Présenteur

C’est une interface pure, elle n’a pas besoin de dériver d’autres interfaces du framework GWT. A l’instar de l’interface de la Vue qui est conçue en fonction des besoins de mise à jour que l’implémentation du Présenteur souhaite opérer sur la Vue, l’interface du Présenteur est construite autour de la même logique sauf que là les rôles sont inversés : L’implémentation de la Vue devient l’appelant et le Présenteur devient l’appelé.

Elle doit disposer de toutes les méthodes nécessaires à la Vue pour que celle-ci puisse déléguer au Présenteur les événements utilisateurs que l’on souhaite traiter dans notre application.
Par exemple, lorsque l’utilisateur renseigne les informations demandées sur la Vue de création de compte utilisateur et qu’il clique sur le bouton de connexion, la Vue reçoit l’événement.
Pour que la Vue puisse effectuer cette délégation, l’interface du Présenteur doit exposer une méthode répondant au cas d’utilisation de la création d’un compte utilisateur :

public interface Presenter {
    public abstract void register(User user);
}

Les méthodes de l’interface du Présenteur n’ont généralement pas de type de retour. Cela s’explique par le fait que la Vue n’a pas à décider d’elle-même si elle doit se mettre ou non à jour et avec quelle information. Si une mise à jour des informations affichées sur la Vue est nécessaire suite au traitement du Présenteur, c’est au Présenteur de la demander. Nous allons discuter de ce sujet dans le point suivant.

2. Implémenter l’interface du Presenter

Pour répondre à la requête de la Vue, le Présenteur réalise jusqu’à 3 types d’opération dans ses méthodes d’implémentation :
– Les traitements métier
– La mise à jour de la Vue en fonction du résultat des traitements métier effectués
– La navigation vers une autre place.

 

  • Les traitements métier

 

Ces traitements contiennent la logique de notre couche Web : validation, transformation des informations saisies par l’utilisateur, calcul, récupération d’informations depuis une base de données, persistance d’informations dans une base de données, etc…
Le Presenter peut effectuer la logique lui-même ou en déléguer une partie ou l’intégralité à d’autres objets, comme par exemple un service RPC.
Par exemple, pour traiter une requête de connexion d’un utilisateur à son compte, la méthode signIn du Présenteur peut faire appel à un service asynchrone de gestion des utilisateurs :

public class SignInActivity extends AbstractActivity implements SignInView.Presenter {
 
private UserServiceAsync userService;
private SignInView signInView;
. . . 
@Override
      public void signIn(String username, String password) {
 
	userService.signIn(username, password, new  
        AsyncCallback<User>() {
	. . .
	});
    }

 

  • La mise à jour de la Vue en fonction du résultat des traitements métier effectués

 

Dans une application, lorsque l’utilisateur interagit avec la Vue pour réaliser un cas d’utilisation, il s’attend généralement à voir le résultat de son action : message de notification, mise à jour des informations affichées, message d’erreur, etc…
Dans une application GWT MVP Activity And Place, les actions utilisateurs sont capturées par la Vue qui en délèguent le traitement à effectuer au Présenteur.
Mais nous n’avons toujours pas vu comment la Vue était ensuite mise à jour.
Cette mise à jour s’effectue sur ordre du Présenteur au moment où ce dernier a terminé le traitement de l’action utilisateur.
Pour continuer notre exemple sur la connexion d’un utilisateur à son compte, lors du callback du service asynchrone de gestion des utilisateurs, le Présenteur peut mettre à jour la Vue avec un message d’erreur si les identifiants de l’utilisateur ne trouvent aucune correspondance dans notre système d’information :

public class SignInActivity extends AbstractActivity implements SignInView.Presenter {
. . .
   userService.signIn(username, password, new AsyncCallback<User>() {
 
	. . .
 
	    @Override
	    public void onFailure(Throwable caught) {
		if (caught.getClass().equals(AuthenticationException.class)) {
		    AuthenticationException authentException = (AuthenticationException) caught;
		    signInView.displayErrorMsg(authentException .getMessage());
		}
	    }
	});
. . .
}

 

  • La navigation vers une autre place

 

Dans certains cas, les traitements de l’action utilisateur par le Présenteur n’aboutissent pas à une mise à jour de la Vue actuellement affichée, ils peuvent en effet déboucher à la navigation vers une nouvelle place et probablement un nouveau Présenteur/Activité et une nouvelle Vue.

Pour prolonger notre exemple de connexion, si l’utilisateur est authentifié avec succès par le système de gestion des utilisateurs, il s’attend sans nul doute à été dirigé vers une autre Vue, par exemple, la Vue de l’espace client. Dans MVP Activity And Place, le passage d’une Vue à une autre, passe par les mécanismes de navigation du framework. L’élément déterminant la navigation est la Place, qui constitue en quelque sorte un marqueur de localisation de l’utilisateur dans l’application.
Nous étudierons plus en détail le sujet de la navigation dans le cours suivant. Pour l’instant, ne cherchons pas à comprendre les mécanismes derrière la notion de Place.
Le framework fournit un objet PlaceController qui permet de requêter la navigation vers une place. Utilisons-le pour diriger notre utilisateur authentifié vers son espace client :

public class SignInActivity extends AbstractActivity implements SignInView.Presenter {
. . .
   userService.signIn(username, password, new AsyncCallback<User>() {
 
	    @Override
	    public void onSuccess(User user) {
		   clientFactory.getPlaceController().goTo(new ClientHomePlace());
	    }
 
	. . .
 
	});
...
}

c) Instancier le Présenteur

L’implémentation du Présenteur ne fait qu’un avec celle de l’activité.
Néanmoins, nous l’avons vu plus haut, l’aspect Activité prend le pas sur l’aspect Présenteur en ce qui concerne le nommage de la classe. Dans la continuité du raisonnement, l’instanciation de la classe ne fait pas exception.
Aussi, le sujet de l’instanciation du Présenteur/activité a davantage sa place dans le prochain cours qui traite des composants de Navigation et de localisation, dont l’activité fait partie.

d) Exemple : Aspect Présenteur de création d’un compte utilisateur

Certains éléments ayant trait à l’aspect Activité étant indispensables pour rendre l’implémentation de notre Présenter opérationnelle au regard du MVP Activity and Place, cet exemple dévoile certaines informations de l’aspect Activité que nous avons survolé jusque là mais que nous étudierons plus en détail dans notre prochain cours : la classe AbstractActivity dont dérive les implémentations de nos activités et la méthode start() qui démarre notre activité.

Interface du Présenteur :

public interface Presenter {
    void register(User user); 
}

Implémentation du Présenteur :

package david.haccoun.teaching.gwt.client.core.register;
 
import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
 
import david.haccoun.teaching.gwt.client.ClientFactory;
import david.haccoun.teaching.gwt.client.core.common.model.User;
import david.haccoun.teaching.gwt.client.core.register.view.RegisterView;
import david.haccoun.teaching.gwt.client.service.UserService;
import david.haccoun.teaching.gwt.client.service.UserServiceAsync;
import david.haccoun.teaching.gwt.shared.RegisterException;
 
public class RegisterActivity extends AbstractActivity implements RegisterView.Presenter {
 
    private ClientFactory clientFactory;
    private RegisterView registerView;
    private UserServiceAsync userServiceAsync = GWT.create(UserService.class);
 
    public RegisterActivity(ClientFactory clientFactory) {
	this.clientFactory = clientFactory;
    }
 
    /**
     * Invoked by the ActivityManager to start a new Activity
     */
    @Override
    public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
	registerView = clientFactory.getRegisterView();
	registerView.reset();
	registerView.setPresenter(this);
	containerWidget.setWidget(registerView);
    }
 
    @Override
    public void register(User user) {
	userServiceAsync.register(user, new AsyncCallback<Void>() {
 
	    @Override
	    public void onSuccess(Void result) {
		registerView.displaySuccessMsg("Your account is created");
	    }
 
	    @Override
	    public void onFailure(Throwable caught) {
		if (caught.getClass().equals(RegisterException.class)) {
		    RegisterException exception = (RegisterException) caught;
		    registerView.displayErrorMsg(exception.getErrorMsg());
		}
	    }
	});
    }
}

Dans ce cours, nous avons étudiés le rôle des composants MVP (Modèle, Vue et Présenteur) du framework MVP Activity And Place de GWT.
Néanmoins, ces composants ne suffisent à réaliser une application fonctionnelle MVP Activity And Place.
En effet, il faut désormais faire vivre nos composants Présenteurs. C’est le rôle des composants de navigation et de localisation que nous allons étudié dans le prochain cours : cours n° 2 : Comprendre et développer les composants de navigation et de localisation d’une application GWT MVP Activity And Place


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 © . Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

2 réponses à cours n° 1 : Comprendre et développer le modèle, la vue et le présenteur d’une application GWT MVP Activity And Place

  1. majid Z dit :

    Excellent !

  2. sdr dit :

    J’attends le cours 2 avec impatience !!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *