1. WebService Dans le TP6, les avis étaient stockés dans une base SQL. Cette semaine les n-uplets sont stockés sur une base de données externe gérée par un serveur HTTP sur lequel tournent des scripts PHP et un moteur PostgreSQL : c est ce qu on appelle un Web Service. L application Android va utiliser des requêtes GET et POST pour y accéder. Le serveur répondra par des données encodées en JSON. Normalement, un WebService utilise d autres types de requêtes, PUT et DELETE pour manipuler les données, mais ce serait trop complexe pour ce TP. Ici, tout ce qui est consultation sera fait par des GET et tout ce qui est modification par des POST. Copiez le projet AvosAvisSQL, renommez-le en AvosAvisWeb. Le principe va être de remplacer la classe SQLiteDatabase par une classe appelée RemoteDatabase qui permet d accéder aux scripts PHP et décoder leurs réponses. 1.1. Scripts PHP sur le serveur Dans la première version de ce TP, les étudiants devaient créer eux-mêmes les scripts PHP dans leur dossier Web W: mais cela prenait beaucoup trop de temps avant de bien marcher. Donc, maintenant, les scripts vous sont fournis opérationnels sur un serveur de l IUT. Il y a également des formulaires HTML permettant d interroger et modifier la base de données. Tous les étudiants vont partager cette même base de données au même moment, c est ce qui en fait l intérêt. Son URL de base est : <http://pnerzic.iut-lannion.fr/> À noter que toutes les modifications que vous faites sur la base de données sont enregistrées avec votre adresse IP et votre login ENT. Retenez vous d écrire n importe quoi, même si c est trop marrant. Vous serez pénalisé si on trouve des textes inappropriés, même effacés ultérieurement. Le serveur possède plusieurs scripts PHP permettant de gérer les avis. Certains de ces scripts doivent être appelés avec une requête GET (consultation des données) et d autres avec un POST (modification). La page d index permet de faire quelques manipulations à l aide de formulaires. 1.1.1. Requêtes de type GET 1.1.2. get_all_avis.php Ce script sans paramètre retourne la liste JSON de tous les avis de la base. Par exemple, si on ouvre l URL http://pnerzic.iut-lannion.fr/get_all_avis.php, on obtient (imaginez qu il y a ces données) : [[1,"test1","2.5","marcel",1425803166000],[2,"test2","4","jean",1426494541000]] C est la représentation JSON de deux n-uplets (_id,libelle,note,auteur,timestamp). Cette liste devra être décodée et transformée en curseur par l application Android. Notez que les dates sont sous forme d un timestamp (type long en Java). 1
1.1.3. get_avis.php Ce script retourne seulement l un des avis. L identifiant doit être fourni dans le paramètre _id du get. Par exemple, l URL http://pnerzic.iut-lannion.fr/get_avis.php?_id=2 affiche : [2,"test2","4","jean",1426494541000] Essayez différents identifiants dont des mauvais et des absents. Il faudra prévoir ces possibilités dans votre application Android pour ne pas planter. 1.1.4. Requêtes de type POST Ces requêtes permettent de modifier les données de la base. Des paramètres précis sont à placer dans le corps de la requête POST voir plus loin comment on construit une requête POST en Android. S il manque un paramètre ou qu il est mal nommé, le script ne fera rien. 1.1.4.1. insert_avis.php Ce script ajoute un nouvel n-uplet dans la base. Il s attend à recevoir différents paramètres fournis par la requête : libelle : le libellé de l avis à ajouter dans la table, note : sa note, c est un réel, auteur : le nom de l auteur de l avis, date : la date de validation de l avis, sous forme d un nombre de millisecondes depuis 1970. 1.1.4.2. update_avis.php Ce script modifie le n-uplet désigné par l identifiant : _id : identifiant du n-uplet à modifier, libelle : le nouveau libellé de l avis, note : sa nouvelle note, auteur : le nom de l auteur de l avis, date : la nouvelle date de validation de l avis, toujours sous forme d un nombre de millisecondes depuis 1970. 1.1.4.3. delete_avis.php Ce script supprime le n-uplet désigné par l identifiant fourni en paramètre : _id : identifiant du n-uplet à supprimer. 1.2. Application Android On revient côté client Android. Le but est de remplacer la base de données SQLiteDatabase par la classe RemoteDatabase qui permet de faire des requêtes POST et GET sur des scripts PHP et de décoder leurs réponses. Pour commencer, rajoutez l autorisation d accéder à internet dans le fichier manifeste. Si vous l oubliez, vous aurez l erreur Connection refused. NB: pour tous les essais avec une vraie tablette, il faudra vous connecter au réseau Wifi eduspot à l aide de vos identifiants ENT. 2
1.2.1. Ouverture de la base de données Dans MainActivity et EditActivity, remplacez SQLiteDatabase par RemoteDatabase partout, ce qui va causer des erreurs tant que tout n est pas modifié. Ensuite, il faut supprimer le helper des deux activités. On n en a plus du tout besoin. Ça va également générer des erreurs, mais vous supprimerez toutes les références au helper. Enfin, il est inutile de fermer la base dans ondestroy car elle ne mémorise aucun état. À la place, voici les instructions qui permettent d «ouvrir» la base distante. Il suffit de donner l URL du serveur, et c est à faire dans les deux activités, MainActivity et EditActivity : // fournir la base d'url pour arriver sur les scripts PHP bdd = new RemoteDatabase("http://pnerzic.iut-lannion.fr"); Si vous regardez le source de ce constructeur, vous verrez qu il ne fait que mémoriser cette chaîne. Elle permet ultérieurement de construire les URLs des scripts PHP. 1.2.2. Interrogation de la base distante C est dans la classe EditActivity qu il y a le changement le plus important. Le problème essentiel, c est que les requêtes HTTP doivent être faites de manière asynchrone : on ne peut pas afficher les informations dans les vues tout de suite parce qu on ne les a pas sous la main. On doit les demander au serveur, et c est seulement quand il répondra qu on pourra afficher quelque chose. Cependant, il ne faut pas rester à attendre dans le thread principal. On doit donc faire appel à un mécanisme d écouteur et de tâches asynchrones dans la classe RemoteDatabase. Ses méthodes get et post envoient une requête au serveur, ça va prendre un certain temps, mais on sort tout de suite (résultat void). C est seulement lorsque le serveur retourne les résultats qu un écouteur onexecsqlfinished est déclenché. Son paramètre contient les données JSON venant du serveur. Voici par exemple comment il faut transformer la méthode display. Elle fait appel à la méthode TableAvis.getAvis à laquelle on rajoute un paramètre : un écouteur qui réagit quand les données sont reçues du serveur. Cet écouteur est le 3 e paramètre de la méthode, après la bdd et l identifiant. Auparavant, avec SQL en local, on avait quelque chose comme ça : public void display() // rien à afficher si l'item est en création if (identifiant < 0) return; // récupérer l'avis Avis avis = TableAvis.getAvis(bdd, identifiant); // afficher ses attributs dans les vues etlibelle.settext(avis.getlibelle());... setdatetotextview(tvdate, avis.getdate()); 3
Maintenant, avec le WebService, on a : public void display() // rien à afficher si l'item est en création if (identifiant < 0) return; // lancer la récupération de l'item désigné par l'identifiant TableAvis.getAvis( // base de données bdd, // identifiant du n-uplet à récupérer identifiant, // écouteur new RemoteDatabaseListener() @Override public void onexecsqlfinished(string jsondata) // construire un avis avec les données JSON Avis avis = TableAvis.fromJSON(jsondata); if (avis == null) return; ); // afficher ses attributs dans les vues etlibelle.settext(avis.getlibelle());... setdatetotextview(tvdate, avis.getdate()); C est à dire que la partie «affichage des attributs du n-uplet» est déplacée dans l écouteur fourni à la méthode getavis. Ce code fait appel à la méthode getavis(bdd, identifiant, listener) dans la classe TableAvis. Dans le TP6, il n y avait pas d écouteur et l avis était retourné immédiatement. Maintenant, son travail consiste à lancer une requête HTTP Get vers un script PHP qui retournera ultérieurement le n-uplet encodé en JSON à l écouteur. Voici son source : public static void getavis( RemoteDatabase bdd, long id, RemoteDatabase.RemoteDatabaseListener listener) // paramètres de la requête ContentValues params = new ContentValues(); params.put(id, id); // faire une requête pour récupérer l'avis identifié par id // elle préviendra l'écouteur quand elle sera finie 4
bdd.get("get_avis.php", params, listener); Comme dans le TP6, elle construit un ContentValue pour contenir l identifiant. Ensuite, au lieu de créer un curseur sur une requête SQL, elle appelle la méthode get de la base de données, en fournissant le nom du script PHP avec ses paramètres et un écouteur. Cette dernière est asynchrone : elle démarre la requête mais n attend pas les résultats ; le listener sera appelé quand ils seront disponibles. Le résultat est donc void. L écouteur déclenche la méthode onexecsqlfinished(string jsondata) qui affiche les données. 1.2.3. Modification dans la base distante Il y aura le même type de transformation dans valider et dans onsupprimer. Mais voici d abord le principe avec la requête d insertion d un nouvel avis dans la classe TableAvis : public static void insertavis( RemoteDatabase bdd, Avis avis, RemoteDatabase.RemoteDatabaseListener listener) // paramètres de la requête ContentValues couples = new ContentValues(); couples.put(libelle, avis.getlibelle());... // requête Post asynchrone, l'écouteur sera prévenu bdd.post("insert_avis.php", couples, listener); Le début est celui que vous avez programmé la semaine dernière avec la base SQLiteDatabase. C est seulement la requête POST qui fait une différence. Vous aurez l écouteur à compléter. Regardez à la fin de la classe EditActivity. Son rôle doit être de quitter l activité avec succès. Remarquez que la méthode insertavis ne peut plus retourner l identifiant du nouvel avis. Ça pourrait poser un problème si on devait s en servir en tant que clé étrangère dans une autre table. 1.2.4. Requête HTTP asynchrone Regardons maintenant comment fonctionne les méthodes get et post de la classe RemoteDatabase. Elle sont basées sur la classe HttpURLConnection. Le principe est de créer une connexion, la configurer, la lancer et retourner la réponse du serveur. La méthode get synchrone vous est fournie, mais à vous de programmer sa variante asynchrone, ainsi que la méthode post asynchrone. La variante asynchrone reçoit un paramètre supplémentaire. C est un écouteur qui doit implémenter la méthode onexecsqlfinished. L idée est de réveiller cet écouteur quand le serveur a répondu à la requête. Il faut savoir que cet écouteur peut être null ; c est quand on veut faire une action asynchrone mais sans se soucier de la réponse du serveur. Regardez d abord la classe HttpGetTask. Son constructeur reçoit un écouteur en 3e paramètre et l enregistre dans une variable membre. Il y a aussi le nom du script et les paramètres. Ensuite, 5
regardez la méthode onpostexecute. Elle reçoit une chaîne en paramètre et la fournit à l écouteur. Regardez ce que fait l écouteur dans le cas de la méthode getallavis. Elle construit un curseur en mémoire pour contenir la réponse du serveur. Vous avez maintenant la méthode doinbackground à compléter. Elle doit : 1. Définir l URL de la requête, incluant les paramètres correctement encodés, 2. Ouvrir une connexion HTTP, 3. Configurer timeouts et methode, 4. Lire la réponse du serveur, 5. Déconnecter la connexion (c est fait dans le finally). Pour savoir comment faire, allez voir le source de la méthode get synchrone. Ensuite, c est similaire pour la méthode post asynchrone utilisant la classe HttpPostTask, mais cette fois, il faut fournir des données. Relisez le cours, c est dedans. Vérifiez que vous avez programmé toutes les lignes commentées avec TODO. 1.2.5. Rafraîchissement régulier des données Comme la base de données est partagée entre plusieurs utilisateurs, chacun pouvant rajouter/modifier/supprimer des éléments, il faut trouver un moyen de mettre à jour la liste sur votre tablette si elle change sur le serveur. On pourrait imaginer un dispositif, type «push», où le serveur prévient toutes les tablettes en cas de changement dans la base, mais il semble difficile de le réaliser. Il faudrait qu une tablette s abonne, comme à une liste de diffusion, pour être informée des changements, mais aussi résilie son abonnement quand on quitte l application. D autre part à plus bas niveau, il faudrait que les tablettes acceptent des connexions entrantes. L autre solution, type «pull» consiste à rafraîchir régulièrement la liste. Ça peut faire perdre du temps au serveur s il y a beaucoup de tablettes actives et des demandes fréquentes. L autre inconvénient, c est que la liste n est pas à jour entre deux rafraîchissements, s ils ne sont pas assez fréquents. Enfin, il faudrait qu il y ait la notion de transaction, afin de garantir la bonne réalisation d une séquence d actions telles que la lecture et la modification d une information pour qu un autre utilisateur ne supprime pas l information que vous êtes en train de modifier. Pour mettre à jour la liste régulièrement, il faut seulement créer une tâche lancée périodiquement qui demande à la liste de recontacter le serveur. Voici les étapes à mettre en place dans oncreate de MainActivity : 1. Créer un Handler, 2. Créer un Runnable, a. Son travail consiste à rafraîchir la liste, c est à dire redémarrer le chargeur de curseur, b. Il se relance 30 secondes plus tard (délai raisonnable), 3. Activer ce Handler. Voir le cours pour le détail du code. 6
1.2.6. Suite du travail Votre travail consiste à finir la transformation de l application et surtout à bien comprendre comment l ensemble fonctionne. 2. Travail à rendre Avec le navigateur de fichiers, descendez dans le dossier app/src du projet AvosAvis du TP7. Rajoutez un fichier appelé exactement IMPORTANT.txt dans le sous-dossier main si vous avez rencontré des problèmes techniques durant le TP : plantages, erreurs inexplicables, perte du travail, etc. mais pas les problèmes dus à un manque de travail ou de compréhension. Décrivez exactement ce qui s est passé. Le correcteur pourra lire ce fichier au moment de la notation et compenser votre note. Cliquez droit sur le dossier main, choisissez Compresser..., format tar.gz, cliquez sur Creer. Déposez l archive main.tar.gz sur Moodle au bon endroit. 7