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.
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ésentation
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 ?
Recyclage
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 :
- téléchargez YMCA v5 (à la fin de cet article),
- téléchargez Grails 1.2 : http://www.grails.org/Download,
- faites pointer la variable d’environnement GRAILS_HOME vers le répertoire d’installation de Grails,
- ajoutez GRAILS_HOME/bin au path,
- en ligne de commande, placez vous dans ymca/backend/zencontactdemo,
- $ grails run-app
- et le tour est joué : http://localhost:8080/zencontactdemo/contact/listAsJson !
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 dong
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 !
Comme d’habitude :
- le code source de l’application : YMCA_code_5
- le tag sur le svn Google Code : http://excilys.googlecode.com/svn/projects/ymca/tags/ymca_article_5/
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.


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 ?