Accueil > Non classé > Android pour l’entreprise – 5 – ZenContact, JSON relax avec Gson

Android pour l’entreprise – 5 – ZenContact, JSON relax avec Gson

Cet article est la suite de Android pour l’entreprise – 4 – Injection de dépendances. Il appartient à la série Android pour l’entreprise, dont le fil conducteur est la réalisation d’une application Android d’annuaire d’entreprise : YMCA.Logo YMCA

Dans cet article, nous allons étudier comment consommer facilement des services REST JSON au sein d’une application Android, à l’aide du framework Gson.

Services “Reste djéïzone”, mékeskidit ?

Rafraichissons-nous la mémoire :

  • REST, c’est un type d’architecture de services Web. Les services deviennent des ressources, identifiées par une URI, et sur lesquelles on peut réaliser des opérations.
  • JSON, c’est un format de données qui présente l’avantage d’être simple à utiliser, compact, et lisible par l’homme. Il est souvent utilisé dans les applications Web notamment parce qu’il s’intègre bien avec JavaScript (on me souffle dans l’oreillette que JSON signifierait JavaScript Object Notation, quelle coïncidence ;) !).

L’idée ici est de consommer un service accessible à une URI spécifique et retournant une liste de contacts au format JSON.

ZenContact

PrésentationGreen Programming !

Dans un premier temps, il nous faut donc un producteur de services REST JSON fournissant la liste de contacts. L’écologie est à la mode, je vous propose donc de faire du green programming : recycler le backend ZenContact développé par Nicolas Martignole, alias Le Touilleur Express.

Petite explication : lors de la soirée web du Paris JUG du 10 mars 2009, Carl Azoury et Nicolas André, travaillant tous deux chez Zenika, ont développé en Wicket une application web de gestion de contacts : ZenContact.

Dans le cadre d’une série d’articles sous forme de tutoriel, Nicolas Martignole a récemment réalisé une version Grails de ZenContact. Le plus beau, c’est qu’il a mis en place au sein de l’application un service permettant d’obtenir la liste de contacts au format JSON. Vous commencez à comprendre ?

RecyclageGrails

Le recyclage nécessite bien souvent une transformation du produit. ZenContact n’échappe pas à la règle : les contacts ne contiennent ni numéro de téléphone, ni adresse. Such a shame!

Qu’à cela ne tienne, Grails est un framework agile, nous allons faire évoluer l’application en deux coups de cuillère à pot !

Tout d’abord, ajouter les champs telephone et adresse à l’entité Contact :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Contact {
  String nom
  String prenom
  Date dateNaissance
  String email
  String telephone
  String adresse

  static constraints={
    prenom(blank:false)
    nom(blank:false)
    dateNaissance(nullable:true)
    email(email:true,nullable:true,blank:true,unique:true)
    telephone(nullable:true)
    adresse(nullable:true)
  }
}

Cela pourrait suffire, mais si l’on veux pouvoir éditer les contacts, il nous faut aussi modifier les vues :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<tr class="prop">
  <td valign="top" class="name">
    <label for="telephone"><g:message code="contact.telephone.label" default="Téléphone"/></label>
  </td>
  <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'telephone', 'errors')}">
    <g:textField name="telephone" value="${contactInstance?.telephone}"/>
  </td>
</tr>
<tr class="prop">
  <td valign="top" class="name">
    <label for="adresse"><g:message code="contact.adresse.label" default="Adresse"/></label>
  </td>
  <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'adresse', 'errors')}">
    <g:textField name="adresse" value="${contactInstance?.adresse}"/>
  </td>
</tr>

Ca y est, ZenContact est prêt pour intégrer YMCA, et disponible sur le svn du blog ! J’en ai même profité pour corriger un bug.

Déploiement

Ce qu’il y a de bien avec Grails, c’est que même mamie peut déployer l’appli :

JSON relax avec Gson

Gson est un framework Java permettant de sérialiser en JSON des entités Java, et inversement.

Le fonctionnement de base est extrêmement simple : une entité est sérialisée sous la forme d’un ensemble de clés / valeurs correspondant à ses attributs. Un exemple :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyEntity {

  private String name;

  public MyEntity(String name) {
    this.name = name;
  }

  public String sing() {
    return "Heyyyy "+name;
  }
}
[...]
MyEntity entity = new MyEntity("Jude");

Gson gson = new Gson();

String json = gson.toJson(entity)
System.out.println(json); // {"name":"Jude"}

MyEntity entity2 = gson.fromJson(json, MyEntity.class);
System.out.println(entity2.sing()); // Heyyyy Jude

Bien entendu, il est possible de régler très finement la sérialisation/désérialisation, et même de le faire manuellement pour certaines classes.

Il ne vous reste plus qu’à le télécharger : http://code.google.com/p/google-gson/downloads/list !

Edit : l’expérience m’a finalement montré qu’il est préférable d’utiliser Jackson plutôt que Gson sur Android, c’est d’ailleurs l’objet de l’article suivant!

Mais comment qu’on dit ?

Jeu : savez-vous faire la distinction à l’oral entre JSON et Gson ?

  • Pour les anglophones, c’est “djéïzone” (JSON) versus “djizone” (Gson).
  • Pour les anglophobes franglicistes, c’est plutôt “jizon” (JSON) versus “jézon” (Gson).

Intégration YMCA / ZenContact

Il ne reste plus qu’à faire évoluer YMCA pour créer une implémentation de ContactService allant se brancher sur le backend ZenContact.

Dans un premier temps, nous allons abstraire l’entité Contact avec une interface ne comportant que des getters, et créer une implémentation ZenContact spécifique à Gson qui suit le contrat défini par le backend.


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
public interface Contact extends Serializable {

        String getName();

        String getPhoneNumber();

        String getAddress();

        String getEmailAddress();

}
[...]
public class ZenContact implements Contact {
        private static final long serialVersionUID        = 1L;

        private ZenContact() {} // Constructeur privé, et oui !

        private String  prenom;
        private String  nom;
        private String  telephone;
        private String  adresse;
        private String  email;

        public String getName() {
                return prenom+ " " + nom;
        }

        public String getPhoneNumber() {
                return telephone;
        }

        public String getAddress() {
                return adresse;
        }

        public String getEmailAddress() {
                return email;
        }
}

Notez qu’on aurait aussi pu garder les noms d’attributs en anglais et spécifier le nom JSON grâce à l’annotation @SerializedName, par exemple en posant @SerializedName("prenom") sur l’attribut firstName.

Service lifté

Voici la version ZenContact du ContactService, avec en prime un doigt de Guice.


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
public class ZenContactService implements ContactService {
        private static final String TAG = ZenContactService.class.getSimpleName();

        @Inject
        private Gson gson;

        @Inject
        private Provider<HttpClient> httpClientProvider;

        /**
         * Url du serveur distant mettant à disposition la liste de contacts.
         */

        @InjectResource(R.string.zencontact_host)
        private String host;

        @Override
        public List<Contact> getContactList() throws ServiceException {

                HttpClient httpClient = httpClientProvider.get();

                String url = host + "/contact/listAsJson";

                HttpUriRequest request = new HttpGet(url);

                Reader reader = null;
                try {
                        HttpResponse response = httpClient.execute(request);

                        InputStream is = response.getEntity().getContent();

                        reader = new InputStreamReader(is);

                        return gson.fromJson(reader, new TypeToken<List<ZenContact>>() {}.getType());
                } catch (IOException e) {
                        Log.e(TAG, "Could not get Json content", e);
                        throw new ServiceException(R.string.io_exception_contact_service_zen);
                } catch (JsonParseException e) {
                        Log.e(TAG, "Could not parse Json result", e);
                        throw new ServiceException(R.string.parse_exception_contact_service_zen);
                } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "Error when closing reader", e);
                }
            }
                }
        }
}

Laïus : type erasure

Avez-vous remarqué la partie désérialisation : gson.fromJson(reader, new TypeToken<List<ZenContact>>() {}.getType()); ?

C’est une technique, que l’on retrouve aussi au sein de Guice, pour pouvoir manipuler des types paramétrés au runtime. En effet, on indique ici à Gson qu’il doit désérialiser le flux pour créer une List de ZenContact. On doit fournir un objet Type à Gson, et il n’est bien entendu pas possible d’écrire List<ZenContact>.class.

Les paramètres de généricité d’une instance ne sont pas disponibles au runtime. Soit. Par contre, il est possible de récupérer ceux d’une classe paramétrée. TypeToken<T> est une classe générique et abstraite de Gson qui à l’instanciation va déterminer le type paramétré par la classe qui l’étend. Ce type peut lui même être paramétré. En l’occurrence, on crée une classe anonyme qui étend TypeToken (d’où les {}). La méthode getType() renvoie le type paramétré (List<ZenContact>).

Binding ding donggunther

Pour finir, modifions le binding Guice :


1
2
3
// bind(ContactService.class).to(RemoteFileContactService.class);
bind(ContactService.class).to(ZenContactService.class);
bind(HttpClient.class).to(DefaultHttpClient.class);

Au passage, on voit que Guice est capable d’injecter un Provider<HttpClient> alors qu’on définit un binding directement sur HttpClient.class.

Et c’est terminé, YMCA utilise désormais le backend ZenContact !

YMCA et ZenContact

Comme d’habitude :

Conclusion

Cet article aura permis de démontrer qu’il est possible de mettre en place très rapidement une application Android consommant des services REST JSON. Quand au backend, ça peut aussi aller très vite grâce à Grails. A ce sujet, je vous incite vivement à lire la série sur Grails de Cyril Brouillard, ainsi que les articles du Touilleur Express.

Je tiens à remercier d’une part Cyril Brouillard pour le coup de main sur la partie Grails, et d’autre part Nicolas Martignole (Touilleur Express) et Carl Azoury (Zenika) qui m’ont gentiment autorisé à utiliser ZenContact pour les besoins de cet article.

Share
  1. Lucius C.
    08/02/2010 à 11:45 | #1

    Bonjour,

    Tout d’abord merci pour votre série d’articles, qui donne un tout autre horizon au développement d’entreprise pour Android.

    Je m’interroge sur la possibilité d’utiliser la sérialisation Java pour communiquer entre le backend J2EE et notre client Android (puisque celui-ci est bel-et bien du Java). En effet celle-ci présente de nombreux avantages: rapidité de sérialisation / déserialisation, bien moins verbeux que du REST XML/JSON, branchement instantané au sein du langage Java, pas de pertes de typages dans des grappes complexes, partage du modèle de données sans génération de code, etc

    Même si on pourrait s’offusquer de violer de grands principes SOA en couplant nos protocoles inter-applicatifs avec l’implémentation, j’ai l’impression que les avantages sont d’un ordre bien plus pratique et pragmatique. Surtout que monter une architecture de backend avec multi-exposition de services (REST, SOAP, AMF, GWT RPC, etc) est relativement triviale avec un bon container.

    Qu’en pensez-vous ?

  1. 19/02/2010 à 10:27 | #1
  2. 25/02/2010 à 10:03 | #2
  3. 06/05/2010 à 15:07 | #3