Odelia>Technologies

Interactions entre une application Android et une application web Grails

| |

GrailsDroid est le nom de l'application Android présentée dans cet article, servant à montrer comment une application Android, s’exécutant sur un téléphone mobile Android, peut interagir avec une application web développée avec le framework web dynamique Grails.

Après avoir expliqué comment invoquer une action Grails grâce à la classe HttpClient du SDK d'Android, nous montrerons comment mettre en place une liste dynamique dont les données seront chargées « à la demande », lorsque l'utilisateur atteint la fin de la liste ; et nous verrons avec quelle facilité une application Grails peut supporter ce type de scénario en renvoyant juste les données demandées.

Vous pourrez bientôt retrouver le code source de l'application Android au moyen d'un fichier joint à cet article. Il vous est également possible de télécharger et de démarrer l'application GrailsDroid sur votre téléphone mobile Android, en utilisant ce lien, ou bien en scannant le code-barres 2D ci-dessous (généré grâce à notre service Codes 2D) avec une application Android comme Barcode Scanner :

Très simple, l'application GrailsDroid comporte deux écrans : le premier est l'écran principal, affichant un message d’accueil et auquel est associé un menu activable grâce au bouton « Menu » du téléphone ; le second écran est destiné à présenter la liste de données dynamique.

GrailsDroid en activités

L'application Android GrailsDroid a été développée avec l'environnement de développement Eclipse pourvu du plugin Android fourni par Google ; GrailsDroid est donc une application Java.
Celle-ci comporte deux activités Android implémentées par les classes GrailsDroidActivity et DataListActivity.
L'activité GrailsDroidActivity est l'activité principale permettant l'affichage d'un menu comportant deux éléments : le premier, « Appeler », permet de déclencher un appel HTTP vers l'application Grails, tandis que le second, « Afficher liste », conduit à l'affichage d'une nouvelle activité (représentée par la classe DataListActivity) servant à la démonstration de la liste infinie.
Précisons que le fichier manifeste de l'application, AndroidManifest.xml, définit une permission spéciale qui permet à l'application d'effectuer des appels sur Internet :

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

Cela est nécessaire dans notre cas, du fait que l'application GrailsDroid invoque une application web Grails au travers du réseau Internet.

L'application web GrailsBox

L'application web Grails servant de « back-end » est déjà en ligne : il s'agit de notre application GrailsBox ; nous donnons plus loin les éléments essentiels de ce qu’il faut mettre en œuvre dans une application Grails pour les interactions avec une application Android.

Une simple requête

La plateforme Android comportant la librairie Apache HttpClient, légèrement modifiée, on se trouve en territoire connu pour l’envoi de commandes HTTP, et notamment, pour l'invocation de services de style REST. Dans l’application GrailsDroid, la classe RestClient se charge de ces appels.

Pour ce qui est du service REST, et dans le cas d'un simple appel destiné à récupérer des données au format JSON, ce qu'il faut avoir dans notre application Grails est très simple : juste un contrôleur Grails définissant une action.

Rappelons qu'une application Grails intègre d'emblée le support de services web de style REST.
En supposant que vous partiez d'une nouvelle application Grails, la création d'un contrôleur s'effectue en exécutant la commande Grails create-controller <nom_du_controleur> ; à partir de là, vous disposez d'une classe Groovy dans laquelle vous pouvez ajouter de nouvelles actions qui prennent la forme de Closure Groovy.

Revenons à notre action Grails qui a pour tâche de produire les données au format JSON ; celle-ci est définie dans le contrôleur AndroidController de l'application GrailBox ainsi :

import grails.converters.JSON

class AndroidController {
       
    def data = {               
        render ([msg: 'Salut !'] as JSON)
    }
               
}

Ici, l'action data peut être invoquée au moyen d'un simple client HTTP via une URL qui prend la forme générale :

http://<quelque_part>/<app_web>/android/data

où android correspond au nom du contrôleur Grails (Grails en déduit le nom de la classe du contrôleur), et data au nom de l'action de ce contrôleur.

Dans le code l'action data, la méthode render est utilisée pour générer une réponse au format JSON à partir d'une Map Groovy, et dont le type de contenu est text/json.
Le code de l'action produira les données JSON suivantes :

{"msg":"Salut !"}

Du côté de l'appelant, dans l'application GrailsDroid, on utilise la librairie HttpClient pour exécuter une commande HTTP GET avec l'URL décrite précédemment, reprise dans la variable url ci-dessous, tandis que la réponse est d'abord transformée en chaîne de caractères et récupérée dans un objet JSONObject d’Android ; voici un extrait de la méthode RestClient.call :

HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(url);     

// Exécuter la requête
HttpResponse response = httpclient.execute(httpget);

String result = null;
HttpEntity entity = response.getEntity();
if (entity != null) {
    // Lire la réponse JSON
    InputStream instream = entity.getContent();
    result = new JSONObject( convertStreamToString(instream) ).getString("msg");

    // Fermer le flux
    instream.close();
}
return result;

La méthode call renvoie donc la valeur de la clé « msg » de la réponse JSON.
Cet appel a lieu lorsque l'utilisateur sélectionne l'entrée de menu « Appeler » depuis le menu de l'activité principale, et le résultat est ensuite affiché dans une boîte de dialogue construite avec la classe Android AlertDialog.Builder.

Ces données d'exemple ne comportent qu'une paire clé/valeur, mais il est possible de transmettre des données plus complexes, ne serait-ce qu'en transmettant une liste de paires plus conséquente, ou bien de renvoyer des objets métiers converties au format JSON grâce à Grails.
C'est ce que nous allons maintenant examiner.

Une liste infinie

Du côté serveur avec Grails

Commençons par décrire comment les données métiers sont servies par l'application Grails GrailsBox ; celle-ci définit la classe métier Contact suivante :

class Contact {
    String prenom
    String nom
}

Au départ cette classe a été créée en tant que classe métier grâce à la commande Grails create-domain Contact, et nous l'avons complétée avec deux propriétés, prenom et nom. En tant que telle, la persistance des objets de ce type est complètement prise en charge par Grails, et nous disposons de méthodes CRUD (Create Read Update Delete) et de recherche injectées de manière dynamique dans le type Contact.
Une classe contrôleur Grails, ContactController, a également été créée afin de servir les objets Contact au monde extérieur :

class ContactController {
    def jsonList = {   
        render Contact.list(params) as JSON
    }
}

Comment ? Est-ce tout ? C'est un exemple de la puissance du framework Grails : avec la simple action jsonList, les paramètres d'URL passés dans la variable implicite params, ainsi que le style d'architecture RESTful inclus au cœur de Grails, un client pourra obtenir des objets métiers Contact sérialisés au format JSON.

Prenons quelques exemples d'URL.

http://www.grailsworks.com/grailsbox/contact/jsonList?max=5, retournera au format JSON, au plus cinq contacts.

http://www.grailsworks.com/grailsbox/contact/jsonList?offset=3&max=10, renverra les données d'au plus dix contacts, à partir du 4ème contact de la liste des contacts.

http://www.grailsworks.com/grailsbox/contact/jsonList?sort=nom&max=10, ici on retournera également une liste d'au plus dix contacts, celle étant triée selon la propriété nom du type Contact.

Voici à quoi ressemble les données JSON liées à l'URL …/jsonList?max=2 :

[{"class":"com.odelia.android.grails.Contact",
"id":1,"prenom":"prenom0","nom":"nom0"},
{"class":"com.odelia.android.grails.Contact",
"id":2,"prenom":"prenom1","nom":"nom1"}]

Du côté client avec GrailsDroid

Revenons à notre application cliente Android GrailsDroid ; cette fois, pour voir le chargement de la liste en action, vous devez sélectionner l'élément de menu « Afficher la liste », à partir du menu de l'activité principale. A ce moment, une nouvelle activité implémentée par la classe com.odelia.android.grails.DataListActivity est démarrée à partir d’une instance de type android.content.Intent.

Adapter les données

En premier lieu, indiquons que la classe Java Item va être utilisée pour contenir les données qui seront reçues du serveur, et dont les objets seront construits après l'analyse des réponses JSON venant du service Grails ; cette classe comporte les propriétés firstname, lastname et date.

La classe DataListActivity étend la classe Android ListActivity, et de ce fait, bénéficie d'une vue liste (définie dans la classe ListActivity) qui sera présentée à l'utilisateur ; afin de faire le lien entre la liste d'objets Item et la vue liste, un objet de type ArrayAdpater<Item> est créé et défini comme adaptateur de type tableau pour la liste : cela va permettre la manipulation des éléments de la liste comme par exemple, ce qui va être le cas dans l'application, l'ajout de nouveaux éléments, la vue liste étant rafraîchie automatiquement.

Un moyen simple de tester l'affichage de la liste dans l'activité aurait été de créer une instance de ArrayAdpater<Item> ainsi :

ArrayAdapter<Item> adapter = new ArrayAdapter<Item>(this, android.R.layout.simple_list_item_1);

Dans le code ci-dessus, on se base sur une ressource existante d'Android pour servir de modèle à l'affichage d'un élément de liste ; dans ce cas précis, le texte utilisé pour l'affichage de l'élément sera la valeur de retour de la méthode Item.toString().

Mais nous allons utiliser un affichage un peu plus complexe et définir notre propre adaptateur basé sur la classe ArrayAdpater :

public class UserListAdapter extends ArrayAdapter<Item> {

    public UserListAdapter(Context context) {
        super(context, R.layout.row, R.id.text);
    }
       
    @Override
    public View getView (int position, View convertView, ViewGroup parent) {
        View view = super.getView(position, convertView, parent);
                               
        Item item = getItem(position);
               
        TextView dateView = (TextView) view.findViewById(R.id.date);

        dateView.setText(item.getDate().toLocaleString());
               
        return view;
    }

}

La définition de la classe UserListAdapter est liée à la ressource R.layout.row : cette ressource est une ressource Android de type « layout » définie en XML, et désigne le fichier row.xml, présent dans le répertoire res/layout, comme modèle de construction d'un élément de la vue liste.
Ce modèle définit simplement deux contrôles de type TextView : le premier contrôle est destiné à contenir la chaîne de caractères retournée par Item.toString() (c'est pourquoi l'id correspondant, R.id.text, est transmis dans le constructeur de la classe parent de UserListAdapter) ; le second contrôle (d'id R.id.date) est réservée à l'affichage de la date dont la valeur proviendra de la propriété Date de la classe Item.
C'est aussi pourquoi la méthode ArrayAdapter.getView est redéfinie dans la classe UserListAdapter, afin d'afficher la date récupérée de l'élément Item (dont la position est passée en paramètre), dans le contrôle TextView correspondant.

Récupérer et afficher les données

A quel moment récupérer les données à afficher dans la vue liste qui est liée à l’activité DataListActivity ?
La classe com.odelia.android.grails.DataController implémentant l'interface android.widget.AbsListView.OnScrollListener a cette responsabilité : elle est chargée d'initialiser la vue liste à partir des données du serveur, mais aussi de détecter, après un défilement de la liste, si les conditions sont réunies pour requérir de nouvelles données.
C'est pourquoi une instance de DataController est définie comme écouteur de l'événement OnScrollListener de la vue liste, dans la méthode redéfinie DataListActivity.onCreate :

getListView().setOnScrollListener(new DataController(this, adapter));

De même que pour l'exemple de la requête simple émise par l’application Android vers une action Grails (discuté plus haut), DataController s'en remet également à la classe RestClient pour obtenir les données nécessaires à l'affichage de la liste, et plus précisément à sa méthode RestClient.getItems : cette méthode accepte comme paramètre une chaîne de caractères qui est l'URL de l'action Grails à invoquer.
Cette action est l'action jsonList déjà décrite, et dans notre exemple, nous utiliserons les paramètres d'URL offset et max. La valeur à donner au paramètre offset est tout simplement égale au nombre d'éléments présents dans la liste, car cela indique le décalage à appliquer dans la requête devant renvoyer la liste des contacts ; pour le paramètre max, c'est le nombre maximal de contacts demandé.

L'appel lancé au travers de la classe RestClient est effectué de manière asynchrone, ce qui permet d'afficher un message d'attente pour l'utilisateur le temps de la récupération des données ; une fois celles-ci obtenues, le message d'attente disparaît, et les nouvelles données sont ajoutées sous la forme de nouveaux éléments dans la vue liste.
A cette fin, et pour des questions relatives au threading de l'interface graphique Android, nous avons utilisé la classe interne DataController.RequestTask qui est dérivée de la classe AsyncTask (voir Painless threading de Romain Guy). Cette dernière est apparue avec Android 1.5 pour simplifier la gestion de tâches longues qui ont besoin de communiquer avec l'interface graphique de l'application.

En conclusion

Nous avons donc vu comment une application Android pouvait requérir et consommer des données produites par une application Grails, au format JSON, qui constitue un format d'échange de données facile à utiliser.
Libre à vous maintenant d'imaginer des applications Android s'appuyant sur des applications web développées avec Grails !

balises dans Langages et systèmes

AJAX cajo Camel DSL Grails Groovy GSP Java JBI RSS ServiceMix SOA