Dans notre cas, il s'agit donc de modifier (et uniquement elle) la fonction "populateListe".
Etape #1 - Une opération longue.
Récupérer la liste des pays et leur population est une opération potentiellement longue. Elle ne peut donc pas être réalisé dans le même "thread" que celui qui gère l'interface (celui du framework par défaut).
Il est donc formellement interdit de populer les données directement depuis la fonction "populateList"
Nous allons donc passer par une AsyncTask.
L'AsyncTask offre tous les outils pour réaliser une opération asynchrone sans trop s'arracher les cheveux, tant sur la gestion des erreurs que sur celle des threads. La classe a aussi d'autres avantages d'optimisations concernant les threads, mais ce n'est pas le sujet ici.
Code java : | Sélectionner tout |
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 | .... private void populateListe() { ListePaysTask task = new ListePaysTask(); task.execute(); // on démarre la tâche asynchrone } // a noter que la tâche aura besoin de communiquer ses résultats à l'activité, elle ne sera donc pas "statique". // il y a d'autres solutions pour pouvoir néanmoins créer des AsyncTask statiques (notamment à base de listener), mais encore une fois // ce n'est pas le but de ces deux petits articles private class ListePaysTask extends AsyncTask<Void,Void,List<Pays>> { // la fonction qui sera effectuée en "background" @Override protected List<Pays> doInBackground(Void ... params) { ArrayList<Pays> data = new ArrayList<Pays>(); // pour cette étape nous conservons les "mock-data" data.add(new Pays("Test 1",45789999)); data.add(new Pays("Test 2",0)); data.add(new Pays("Test 3",147)); data.add(new Pays("Test 4",999999999)); return data; } // la fonction qui sera appelée avec les données retournées par "doInBackground" dans le thread principal protected void onPostExecute(List<Pays> data) { if (data != null) { // d'ou lintérêt de séparer les fonctions de récupération de données des fonctions de mise à jour de l'interface.... ListePopulationActivity.this.updateListe(data); } else { // une erreur s'est produite Toast.makeText(ListePopulationActivity.this,"Erreur de récupération des données",Toast.LENGTH_LONG).show(); } } } ... |
Voilà... à présent notre activité n'a pas changé d'un iota, mais l'ensemble des données est populée en asynchrone.
Etape #2 - Lecture depuis le webservice.
Nous pouvons donc remplacer les données de test par le véritable appel au web-service.
Seule la classe ListePaysTask (et sa fonction doInBackground) seront donc modifiées:
Code java : | Sélectionner tout |
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 | .... .... @Override protected List<Pays> doInBackground() { try { ArrayList<Pays> data = new ArrayList<Pays>(); // Etape 1: on récupère les données sous forme de String String strData = getStringResult("http://un.example.com/population.php"); // Etape 2: on traduit en JSON JSONObject jsonData = new JSONObject(strData); // Etape 3: on parse le JSON parseListePays(jsonData,data); return data; } catch (Exception ex) { Log.e("ListPaysTask","Erreur de récupération des données !",ex); return null; } } // on part du principe que l'objet contient une array "pays" contenant la liste de tous les pays sous forme d'objet "name" et "population" // throws JSONException sert à indiquer que la fonction risque d'échouer avec cette exception private void parseListePays(JSONObject data, ArrayList<Pays> res) throws JSONException { JSONArray jsonArray = data.getJSONArray("pays"); for (int i = 0; (i < jsonArray.length()); ++i) { JSONObject jsonPays = jsonArray.getJSONObject(i); Pays pays = new Pays(); parsePays(jsonPays,pays); res.add(pays); } } // encore une fois on sépare la fonction, toute modification de "Pays" ne nécessitera alors que la modification de cette fonction-ci. // a noter que dans l'idéal, la classe "Pays" pourrait fournir elle-même la fonction de parsing depuis un objet JSON. private void parsePays(JSONObject data, Pays res) throws JSONException { res.setName(data.getString("name")); res.setPopulation(data.getLong("population")); } ... ... |
Pourquoi faire trois étapes et pas une seule et grosse fonction ? Tout simplement parce qu'on aime en programmation séparer chaque "niveau":
Le web-service nous renvoie une chaine de caractères => fonction getStringResult
Cette chaine est un objet JSON qui contient une liste de pays => fonction parseListPays()
Chaque élément de cette liste est un objet JSON qui correspond à un Pays => fonction parsePays()
Pour tester, la fonction getStringResult peut renvoyer une chaine (sans appel au webserivce) telle qu'elle est attendue...
Par exemple:
Code : | Sélectionner tout |
1 2 3 4 | private String getStringResult(String url) throws ParseException, IOException { return "{\"pays\":[{\"name\":\"Test1\",\"population\":45789999},{\"name\":\"Test 2\",\"population\":0}]}"; } |
Il reste donc une unique fonction à implémenter, l'appel au webservice lui-même, et pour celle-ci, rien de plus simple.
Code java : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | .... .... // a noter le "throws" des exceptions, car cette fonction *peut* échouer ! private String getStringResult(String url) throws ParseException, IOException { HttpClient client = AndroidHttpClient.newInstance("pays-population v1.0"); // on passe un "user-agent" cohérent. HttpGet getRequest = new HttpGet(url); // le "get" lui-même est assez simple. HttpResponse response = client.execute(getRequest); // la réponse est obtenue facilement HttpEntity entity = response.getEntity(); // le contenu est accessible directement return EntityUtils.toString(entity,"UTF-8"); // la lecture sous forme de texte, en utilisant les classes outils. //"UTF-8" signifie que *si* la réponse ne contient pas d'information quant à l'encodage, celui-ci sera considéré comme étant en UTF-8 } .... .... |
Et voilà, notre application est désormais finie, et fonctionnelle.