Android et les web-services
Se connecter au monde extérieur
Définition d'un web-service
Certaines applications fonctionnent sans avoir besoin de connection internet.
D'autres se connectent à des serveurs pour étendre certaines de leurs fonctionnalités.
Le service appellé peut-être développé directement pour l'application, ou être mis à disposition pour n'importe quelle application.
Voici quelques fonctionnalités que peuvent apporter les web-services :
- Gestion des utilisateurs (inscription, vérification de droits, ...)
- Messagerie entre utilisateurs
- Compteurs de visites et statistiques d'utilisation
- Traduction
- Geocoding et reverse géocoding
- Informations météo
- Validation d'adresse
- Connection à des réseaux sociaux
- Affichage de publicités ciblées...
- Paiement
- ...
Il existe de nombreux web-services ouverts au public, on parle aussi d'API. Ils nécessitent souvent l'obtention d'une clé.
Le protocole des Web-Services
S'il est possible de se connecter directement à un serveur avec le protocole de son choix, c'est le protocole HTTP qui est le plus souvent utilisé. C'est le même qui est utilisé sur internet, lorsqu'un navigateur se connecte à un site internet.
Quand un navigateur se connecte à un site internet, le serveur lui renvoie la plupart du temps du contenu HTML, que le navigateur est capable de traiter pour afficher la page voulue, avec des images et une mise en page particulière.
Lorsque notre application se connectera à un serveur, elle recevra du contenu sous différente forme, qui lui faudra décoder puis utiliser.
Le protocole HTTP
Ce sont les mêmes serveurs qui permettent d'acceder à leur donnée en HTML (pour le navigateur web), et en XML ou en JSON pour les applications.
Le principe du protocole HTTP est le suivant :
Un client HTTP avec une application Android
Voici un client HTTP minimal avec Android, permettant de récuperer la réponse du serveur sous forme de chaine de caractère.
L'application doit avoir la permission de se connecter à internet. Il faut donc ajouter cette permission dans le fichier AndroidManifest (dans la balise manifest).
<uses-permission android:name="android.permission.INTERNET" />
Voici un web-service "maison" très simple :
http://nee.free.fr/testApi/test.php -> répondra "Hello, world"
http://nee.free.fr/testApi/test.php?name=Boby -> répondra "Hello, Boby"
try { //construction de la requete HTTP HttpUriRequest request = new HttpGet("http://nee.free.fr/testApi/test.php"); //Initialise le client HTTP HttpClient httpClient = new DefaultHttpClient(); //Envoi la requete et recoit la reponse HttpResponse response = httpClient.execute(request); //traitement du code de la reponse int responseCode = response.getStatusLine().getStatusCode(); System.out.println("http code: " + responseCode); //si ce n'est pas un code d'erreur... if (responseCode < 400) { //recupere le contenu de la réponse String content = EntityUtils.toString(response.getEntity()); System.out.println("http content: " + content); } } catch (Exception e) { System.out.println("Il y a eu un probleme: " + e); }
Exercice :
Réaliser une application qui utilise ce web-service.
L'application affichera le message reçu, ou un message d'erreur générique en cas de problème.
Etapes :
- Créer un nouveau projet avec une Activity
- Ajouter la permission "INTERNET" au projet
- Ajouter le code de connection dans le "onCreate" de l'Activity
- Ajouter le code permettant d'afficher à l'écran (dans un TextView) le message reçu, ou le message d'erreur
- Tester en modifiant le parametre name du web-service
- Tester en mettant volontairement un serveur inexistant
Les limites du format texte
Le fait de recevoir la réponse dans un format non structuré limite le nombre de données que je peux récupérer.
Dans la réalité, il est souvent necessaire d'avoir une réponse plus pratique à manipuler.
On utilise la plupart du temps des formats simples comme le XML ou le JSON pour structurer notre réponse, et du coup pouvoir passer plus d'informations.
Ces formats sont standards, et permettent de structurer n'importe quel type de donnée.
Le JSON
JSON (JavaScript Object Notation) est un format texte qui permet de structurer de maniere légère des données. C'est une alternative au format XML.
Il structure les données liste (pour une série de données) ou en objet (pour un dictionnaire clé/valeur). Ces structures peuvent être combinées.
Exemple d'objet :
{ "nom": "Bob", "mail": "bob@cnam.fr", "age": 54 }
Exemple de liste :
[ "Bob", "John", "Jack" ]
Exemple de liste d'objets :
[ { "nom": "Bob", "mail": "bob@cnam.fr", "age": 54 }, { "nom": "John", "mail": "john@cnam.fr", "age": 12 }, { "nom": "Jack", "mail": "jackiedu13@cnam.fr", "age": 43 } ]
Exemple concret :
{ "data": { "current_condition": [ { "cloudcover": "0", "humidity": "36", "localObsDateTime": "2013-04-15 02:47 PM", "observation_time": "12:47 PM", "precipMM": "0.0", "pressure": "1023", "temp_C": "23", "temp_F": "73", "visibility": "10", "weatherCode": "113", "weatherDesc": [ { "value": "Sunny" } ], "weatherIconUrl": [ { "value": "http://www.worldweatheronline.com/sunny.png" } ], "winddir16Point": "W", "winddirDegree": "270", "windspeedKmph": "11", "windspeedMiles": "7" } ], "nearest_area": [ { "areaName": [ { "value": "Aix" } ], "country": [ { "value": "France" } ], "latitude": "43.533", "longitude": "5.433", "population": "0", "region": [ { "value": "Provence-Alpes-Cote D'azur" } ], "weatherUrl": [ { "value": "http://www.worldweatheronline.com/FR.aspx" } ] } ] } }
JSON et Android
Android inclut des outils pour traiter le JSON : les classes JSONObject et JSONArray.
A partir d'un objet String, on peut parcourir la structure JSON grace à ces objets.
Attention, lorsque l'on tente de traiter une réponse JSON, on s'attends à une structure particulière. Le serveur peut, pour de nombreuses raisons, nous renvoyer des données que l'on attends pas. Il faut donc penser à traiter les cas d'erreurs pour éviter que notre application se crashe.
Exemple simple :
Voici un exemple de réponse d'un web-service :
{ "message": "Hello, Bob !", "name": "Bob", "datation": { "date": "16-04-2013", "horaire": "19:30:54" } }
En utilisant le code du client Http minimal :
//recupere le contenu de la réponse String content = EntityUtils.toString(response.getEntity()); //a partir de la chaine de caractere, on peut parcourir le JSON... JSONObject racine = new JSONObject(content); String message = racine.getString("message"); String nom = racine.getString("name"); //...puis decendre dans la structure JSONObject datation = racine.getJSONObject("datation"); String date = datation.getString("date"); String horaire = datation.getString("horaire"); System.out.println("Message : " + message + ", date : " + date + ", heure : " + horaire);
Message : Hello, world !, date : 16-04-2013, heure : 19:33:52
Exemple sur les données de "World Weather Online"
A partir du JSON du web-service "World Weather Online" (www.worldweatheronline.com/free-weather.aspx) :
{ "data": { "current_condition": [ { "cloudcover": "0", "humidity": "36", "localObsDateTime": "2013-04-15 02:47 PM", "observation_time": "12:47 PM", "precipMM": "0.0", "pressure": "1023", "temp_C": "23", "temp_F": "73", "visibility": "10", "weatherCode": "113", "weatherDesc": [ { "value": "Sunny" } ], "weatherIconUrl": [ { "value": "http://www.worldweatheronline.com/sunny.png" } ], "winddir16Point": "W", "winddirDegree": "270", "windspeedKmph": "11", "windspeedMiles": "7" } ], "nearest_area": [ { "areaName": [ { "value": "Aix" } ], "country": [ { "value": "France" } ], "latitude": "43.533", "longitude": "5.433", "population": "0", "region": [ { "value": "Provence-Alpes-Cote D'azur" } ], "weatherUrl": [ { "value": "http://www.worldweatheronline.com/FR.aspx" } ] } ] } }
Pour récupérer la température en degré Celsius :
On voit qu'elle se trouve dans data/current_condition/0/temp_C.
Il y a 2 niveaux d'objets (data, current_condition), puis un tableau contenant un seul élément, et enfin la propriété (temp_C)
//recupere le contenu de la réponse String content = EntityUtils.toString(response.getEntity()); //a partir de la chaine de caractere, on peut parcourir le JSON... JSONObject racine = new JSONObject(content); JSONObject data = racine.getJSONObject("data"); //data JSONArray conditions = data.getJSONArray("current_condition"); //les elements de current_condition JSONObject firstCondition = conditions.getJSONObject(0); //le premier (et seul) element du tableau int temperature = firstCondition.getInt("temp_C"); //on a notre valeur!
Une autre option plus compacte (sur une seule ligne) :
int temperature = json.getJSONObject("data") .getJSONArray("current_condition") .getJSONObject(0) .getInt("temp_C");
Pour récupérer l'information de la ville :
On voit qu'elle se trouve dans data/nearest_area/0/areaName/0/value.
Il y a 2 niveaux d'objets (data, nearest_area), puis un tableau contenant un seul élément, un tableau (areaName) contenant un seul élément et enfin l'objet contenant la propriété (value)
//recupere le contenu de la réponse String content = EntityUtils.toString(response.getEntity()); //a partir de la chaine de caractere, on peut parcourir le JSON... JSONObject racine = new JSONObject(content); JSONObject data = racine.getJSONObject("data"); //data JSONArray nearestAreas = data.getJSONArray("nearest_area"); //les elements de nearest_area JSONObject firstArea = nearestAreas.getJSONObject(0); //le premier (et seul) element du tableau JSONArray areaNames = firstArea.getJSONArray("areaName"); JSONObject firstAreaName = areaNames.getJSONObject(0); //le premier (et seul) element du tableau String city = firstAreaName.getString("value"); //on a notre valeur!
Une autre option plus compacte (sur une seule ligne) :
String ville = json.getJSONObject("data") .getJSONArray("nearest_area") .getJSONObject(0) .getJSONArray("areaName") .getJSONObject(0) .getString("value");
Pour récupérer la "description" de la prévision :
On voit qu'elle se trouve dans data/current_condition/0/weatherDesc/0/value.
//recupere le contenu de la réponse String content = EntityUtils.toString(response.getEntity()); //a partir de la chaine de caractere, on peut parcourir le JSON... JSONObject racine = new JSONObject(content); JSONObject data = racine.getJSONObject("data"); //data JSONArray conditions = data.getJSONArray("current_condition"); //les elements de current_condition JSONObject firstCondition = conditions.getJSONObject(0); //le premier (et seul) element du tableau JSONArray description = firstCondition.getJSONArray("weatherDesc"); JSONObject firstDescription = description.getJSONObject(0); String description = firstDescription.getString("value"); //on a notre valeur!
Une autre option plus compacte (sur une seule ligne) :
int temperature = json.getJSONObject("data") .getJSONArray("current_condition") .getJSONObject(0) .getJSONArray("weatherDesc") .description.getJSONObject(0) .getString("value");
Exercice :
Réaliser une application qui utilise le web-service JSON de test : http://nee.free.fr/testApi/test.php?format=json.
L'application affichera le message reçu, ainsi que la date et l'horaire, ou un message d'erreur générique en cas de problème.
Etapes :
- Créer un nouveau projet avec une Activity
- Ajouter la permission "INTERNET" au projet
- Ajouter le code de connection dans le "onCreate" de l'Activity
- Ajouter le code permettant de parcourir le JSON reçu pour récuperer les valeurs de "message", "date" et "horaire"
- Ajouter le code permettant d'afficher à l'écran (dans un TextView) le message et la date reçu, ou le message d'erreur
- Tester en modifiant le parametre name du web-service
- Tester en mettant volontairement un serveur inexistant
Exercice :
Réaliser une application qui utilise le web-service de météo (worldweatheronline.com).
Adresse de la documentation de l'API : http://developer.worldweatheronline.com/io-docs
Exemple de positions :
- Aix en provence : latitude=43.529742 longitude=5.447427
- Paris : latitude=48.86 longitude=2.34
Clef pour les tests au CNAM (limité à 3 requetes par seconde et 500 par jour) : wrxysf4jz4e8prtapychkkk8
Les pictos se trouvent dans une archive à télécharger : nee.free.fr/testApi/pictoMeteo.zip
Exemple de requete complète pour Aix :
http://api.worldweatheronline.com/free/v1/weather.ashx ?q=43.529742,5.447427 &format=json &includelocation=yes &key=wrxysf4jz4e8prtapychkkk8
Cahier des charges
Composition de l'application:
- Une seule Activity affichant la prévision (voir capture), et s'occupera de démarrer tous le processus.
- La classe Forecast contiendra seulement les propriétés d'une prévision météo .
- L'interface Forecaster, avec une seule méthode représentera n'importe quel web-service ou tout autre moyen de fabriquer une prévision.
- La classe concrete WorldWeatherForecaster, qui implémente l'interface Forecaster contiendra le code qui utilisera le web-service worldweatheronline.com.
- La classe concrete MockForecaster, qui implémente aussi l'interface Forecaster, nous permettra de tester notre application.
Etapes :
- Créer un nouveau projet "WeatherForecast", avec une Activty de base.
- Ajouter la permission "INTERNET" au manifest.
- Modifier le layout de l'activity pour y placer un linearLayout contenant un TextView, ImageView, TextView, TextView.
- Pour chacun des éléments du layout, ajouter les "id".
- Ajouter les 3 images des pictos dans le dossier res/drawable-mdpi/.
- Créer la classe Forecast contenant les propriétés d'une prévision (String city, int imageResource, String comment, double temperature).
- Dans l'Activity, créer une méthode "showForecast(Forecast f)" qui utilise les données d'un objet Forecast pour les passer aux éléments d'UI (findViewById..., setText, ...).
- Créer l'interface Forecaster
- Créer la classe MockForecaster, qui implemente l'interface Forecaster
- Dans la méthode makeForecast de la classe MockForecaster, instancier et retourner un Forecast avec des valeurs arbitraire.
- Dans le onCreate de l'activity, instancier MockForecaster dans une variable de type Forecaster
- Appeler la méthode makeForecast avec des valeurs quelconques sur l'instance de MockForecaster, et appeller la méthode showForecast avec l'objet Forecast retourné.
- Tester dans l'émulateur !
- Créer la classe WorldWeatherForecaster, qui implemente l'interface Forecaster
- Dans la méthode makeForecast de la classe WorldWeatherForecaster, construire une requette HTTP, l'envoyer, et récuperer la réponse...
- ...traiter le JSON pour extraire toutes les valeurs pour construire une instance de Forecast. Déterminer l'image et le commentaire selon le code reçu.
- Dans le onCreate de l'activity, remplacer l'instance de MockForecaster par une instance de WorldWeatherForecaster
- Tester dans l'émulateur ! C'est terminé !
Annnexe :
Modifier la taille d'un TextView
Ajouter l'attribut dans le XML
android:textAppearance="?android:attr/textAppearanceLarge"
Gérer les images dans les "resources"
Récuperer une image existante (qui se trouve dans res/drawable***/monfichier.png)
int pointeurVersMonImage = R.drawable.monfichier;
Afficher un "drawable" dans une ImageView
monImageView.setImageResource(R.drawable.monfichier); //ou monImageView.setImageResource(pointeurVersMonImage);