Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Android - Communication avec un webservice - Partie 2
Un billet de blog de nicroman

Le , par nicroman

0PARTAGES

Nous avons vu dans le billet précédent comment afficher les données... nous allons voir maintenant comment récupérer celles-ci depuis le webservice.

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 l’inté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}]}"; 
        }
Ensuite l'unique try/catch... Il est évident que si une fonction échoue son seul moyen de retourner cet état de fait est en passant par des exceptions... Hors si l'étape-1 échoue, les étapes suivantes ne peuvent être accomplies. Toute les opérations seront donc protégées avec un seul et unique try/catch. Et l'exception sera loguée de manière correcte, pour éviter de sombrer dans l'oubli.

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.

Une erreur dans cette actualité ? Signalez-le nous !