mercredi 20 octobre 2010

Android JAX-RS Client : partie cliente avec Gson

Dans un post précédent, nous avons vu comment il était possible d'exposer des services JAX-RS JSON grâce au Google appEngine et aux API Java Jersey et Jackson. Nous allons cette fois-ci aborder la partie cliente de ces services sous Android dont voici une capture d'écran :

Téléchargement et installation des outils

Pour reproduire ce tutorial, vous aurez besoin des outils et API suivants :
  • Eclipse : le célèbre IDE
  • SDK Android : pour développer des applications Android avec Eclipse
  • Gson : API de sérialisation / désérialisation made in Google
Une fois téléchargés et installés, créez un nouvel Android Project dans votre workspace Eclipse :


Ajoutez un répertoire /lib à la racine du projet et importez-y la librairie gson.jar. Ajoutez également la dépendance au classpath du projet Eclipse.


Afin d'autoriser notre application à effectuer des appels réseaux via Internet, il faut ajouter au fichier AndroidManifest.xml la permission android.permission.INTERNET :
<?xml version="1.0" encoding="utf-8"?>
<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.androgames.blog.sample.rest.client"
    android:versionCode="1"
    android:versionName="1.0">
      
    <application 
        android:icon="@drawable/icon" 
        android:label="@string/app_name">
    
        <activity 
            android:name=".UserConsumer"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
                  
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
        </activity>

    </application>
    
    <uses-sdk android:minSdkVersion="4" />
    
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

Mise en place de l'interface utilisateur

Notre application sera décomposée en deux parties. L'une permettra la création, la mise à jour et la suppression d'utilisateurs tandis que l'autre affichera la liste de tous les utilisateurs avec leur prénom, nom et id :


Le layout utilisé pour le rendu d'un utilisateur de la liste est définit par le fichier user.xml du répertoire res/layout :
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    
    <ImageView
        android:src="@drawable/user"
        android:layout_width="48dip"
        android:layout_height="48dip"
        android:layout_marginRight="6dip" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:id="@+id/name"
            android:textAppearance="?android:textAppearanceMedium"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:ellipsize="marquee"
            android:gravity="center_vertical" />

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" >

            <TextView
                android:textAppearance="?android:textAppearanceSmall"
                android:textColor="#FFCC3333"
                android:textStyle="bold"
                android:layout_marginRight="6dip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="ID :"
                android:ellipsize="marquee" />
            
            <TextView
                android:id="@+id/id"
                android:textAppearance="?android:textAppearanceSmall"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="6dip"
                android:singleLine="true"
                android:ellipsize="marquee" />
            
        </LinearLayout>
            
    </LinearLayout>

</LinearLayout>
Le formulaire permettant la mise à jour ou la suppression d'un utilisateur existant, ainsi que la création de nouveaux utilisateurs se présentera sous les formes suivantes :



Les actions réalisables sont au nombre de 5 :
  1. Créer un nouvel utilisateur
  2. Enregistrer les modifications apportées au nom ou au prénom d'un utilisateur
  3. Supprimer un utilisateur
  4. Annuler l'édition en cours d'un utilisateur, le formulaire est alors remis à zéro
  5. En cliquant sur un utilisateur de la la liste, le formulaire se met à jour avec les informations de l'utilisateur sélectionné.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    
    <LinearLayout
        android:orientation="vertical"
        android:paddingLeft="6dip"
        android:paddingRight="6dip"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        
        <TextView
            android:text="@string/forName"
            android:textAppearance="?android:textAppearanceLarge"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    
        <EditText 
            android:id="@+id/forName"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
        
        <TextView
            android:text="@string/name"
            android:textAppearance="?android:textAppearanceLarge"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    
        <EditText 
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_gravity="right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            
            <Button 
                android:id="@+id/insertOrUpdate"
                android:onClick="insertOrUpdate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            
            <Button 
                android:id="@+id/delete"
                android:text="@string/delete"
                android:onClick="delete"
                android:visibility="gone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            
            <Button 
                android:id="@+id/initialize"
                android:text="@string/initialize"
                android:onClick="initialize"
                android:visibility="gone"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
                
        </LinearLayout>
    
    </LinearLayout>
        
    <ListView 
        android:id="@+id/users"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:layout_width="fill_parent" />
        
</LinearLayout>
Dans le Layout ci dessus, on remarquera en particulier que les boutons ne sont pas tous présents par défaut. Les boutons "Enregistrer", "Supprimer" et "Annuler" ne sont proposés que si un utilisateur de la liste à été sélectionné. L'attribut android:onClick des balises Button permet de spécifier le nom de la méthode appelée lorsque lorsque le bouton est pressé. Ces méthodes doivent être déclarées dans la ou les Activity qui utilisent le Layout comme content view. Elle doivent être publiques et prendre un unique paramètre de type View.

Pour le rendu de la liste des utilisateurs, nous utilisons un ListAdapter afin de faire le pont entre la ListView (couche de présentation) et notre liste d'utilisateurs (modèle de données).
package net.androgames.blog.sample.rest.client;

import java.util.List;

import android.content.Context;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class UserAdapter extends BaseAdapter {

    private List<User> users;
    private LayoutInflater inflater;

    public UserAdapter(final ListView list, Context context) {
        this.inflater = LayoutInflater.from(context);
        // on attache l'adapter à la ListView
        list.setAdapter(this);
        // raffraichissement de la liste lorsque
        // une donnée est modifiée
        registerDataSetObserver(new DataSetObserver() {
            public void onChanged() {
                list.invalidateViews();
            }
        });
    }

    public int getCount() {
        if (users == null) {
            return 0;
        } else {
            return users.size();
        }
    }

    public Object getItem(int position) {
        return users.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        
        TaskHolder holder;
        
        // récupération du holder
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.user, null);
            holder = new TaskHolder();
            holder.name = (TextView) convertView.findViewById(R.id.name);
            holder.id = (TextView) convertView.findViewById(R.id.id);
            convertView.setTag(holder);
        } else {
            holder = (TaskHolder) convertView.getTag();
        }
        
        // affichage de l'utilisateur d'index demande
        // via l'utilisation du holder
        if (position < getCount()) {
            User user = (User) getItem(position);
            holder.name.setText(user.getPrenom() + " " +user.getNom());
            holder.id.setText(user.getId());
        }
        
        return convertView;
    }
    
    public void setUsers(List<User> users) {
        this.users = users;
        // mise à jour de la liste
        notifyDataSetChanged();
    }
    
    public void addUser(User user) {
        if (!users.contains(user)) {
            users.add(user);
            // mise à jour de la liste
            notifyDataSetChanged();
        }
    }

    public void removeUser(User user) {
        users.remove(user);
        // mise à jour de la liste
        notifyDataSetChanged();
    }
    
    /**
     * Holder class :
     * Permet de ne pas multiplier le nombre
     * d'instance de View utilisées pour
     * l'affichae des utilisateurs dans la
     * ListView
     */
    static class TaskHolder {
        TextView name;
        TextView id;
    }

}
La méthode getView de l'Adapter permet de retourner une instance du LinearLayout utilisé pour le rendu d'un utilisateur (/res/layout/user.xml) et dans lequel les vues sont remplies avec les données de l'utilisateur en position demandée.

Appels aux services REST

L'interface utilisateur maintenant en place, nous allons nous pencher sur les appels aux services REST. Afin de ne pas tomber dans les fameuses erreurs ANR (Application Not Responding), les appels aux services s'exécuteront dans un Thread différent de celui utilisé pour les interactions avec l'utilisateur (rendu graphique, capture des événements, ...). Pour cela, nous utiliserons des AsyncTask afin que l'envoi de la requête au serveur, l'attente de la réponse et la récupération de la réponse s'exécute en parallèle du Thread principal. La méthode est similaire à l'utilisation du de la classe SwingWorker de Java 6.
/**
 * Classe abstraite pour l'envoi de requètes asynchrones au serveur.
 */
public abstract class AbstractTask 
        extends AsyncTask<HttpRequestBase, Void, HttpResponse> {
    
    /**
     * Appelée avant le lancement du traitement en arrière plan.
     * Cette méthode est exécutée dans le Thread appelant.
     */
    protected void onPreExecute() {}
    
    /**
     * Traitement en arrière plan.
     * Cette méthode est exécuté dans un Thread différent
     * du Thread appelant.
     */
    protected HttpResponse doInBackground(final HttpRequestBase...requests) {}

    /**
     * Appelée après la fin du traitement en arrière plan.
     */
    protected void onPostExecute(final HttpResponse response) {}
    
    /**
     * Traitement spécifique du JSON
     * @param in Le contenu de la réponse HTTP OK
     */
    protected abstract void handleJson(final InputStream in);

};
Cette classe nous permet de définir un comportement générique pour chacun des appels aux services REST ainsi qu'un comportement spécifique à implémenter :
onPreExecute
Cette méthode est appelée dans le Thread principal avant que le traitement en tâche de fond ne soit lancé. Dans notre cas, nous l'utiliserons pour afficher une fenêtre popup d'attente.
doInBackground
Cette méthode appelée dans un Thread annexe permet de lancer un traitement en tâche de fond. Nous l'utiliserons pour effectuer les requêtes HTTP à destination du serveur et attendre la réponse de ce dernier. Cette méthode retourne un objet dans le type correspond à celui de la méthode ci dessous.
onPostExecute
Cette méthode est appelée dans le Thread principal une fois que le traitement en tâche de fond a terminé.Elle prend en paramètre le résultat du traitement effectué en tache de fond. Dans notre cas, cette méthode sera utilisée pour analyser la réponse du serveur et traiter le contenu de celle-ci.
handleJson
Cette méthode abstraite doit être surchargée pour traiter de manière spécifique le contenu de la réponse du serveur au format JSON.
Nous utiliserons 4 implémentations différentes de cette classe abstraite, chacune ayant pour but le traitement d'un type particulier de réponse du serveur :
/**
 * Récupération de la liste des User
 */
private class ListUsersTask extends AbstractTask {

    protected void handleJson(final InputStream in) {
        final Type collectionType = new TypeToken<List<User>>(){}.getType();
        List<User> users = null;
        synchronized (lock) {
            users = gson.fromJson(new InputStreamReader(in), collectionType);
        }
        // La liste récupérée initialement est la référence
        // des User pour l'application Android
        adapter.setUsers(users);
    }

};
/**
 * Recuperation d'un utilisateur
 * déjà référencé localement
 */
private class GetUserTask extends AbstractTask {

    protected void handleJson(final InputStream in) {
        synchronized (lock) {
            updateCurrentUser(gson.fromJson(new InputStreamReader(in), User.class));
        }
    }

};
/**
 * Récupération d'un nouvel utilisateur
 * non encore référencé localement
 */
private class AddUserTask extends AbstractTask {

    protected void handleJson(final InputStream in) {
        synchronized (lock) {
            updateCurrentUser(gson.fromJson(new InputStreamReader(in), User.class));
        }
        adapter.addUser(currentUser);
    }

};
/**
 * Suppression d'un utilisateur
 * référencé localement
 */
private class DeleteUserTask extends AbstractTask {

    protected void handleJson(final InputStream in) {
        User fakeUser = new User();
        try {
            fakeUser.setId(new BufferedReader(
                    new InputStreamReader(in, ENCODING_UTF_8)).readLine());
        } catch (Exception e) {}
        adapter.removeUser(fakeUser);
        updateCurrentUser(null);
    }

};
Dans le cas de la suppression d'un utilisateur, le serveur nous renvoie un objet de type String sans délimiteur d'objet JSON : '{' ou '['. Nous traitons donc le contenu directement comme une chaine de caractères au moyen d'un BufferedReader.

Pour lancer ces traitements asynchrones d'envoi des requêtes au serveur, nous devons au préalable construire la HttpRequestBase que nous allons passer en paramètre à l'une de nos AbstractTask. Le code ci-dessous nous permet de mettre à jour l'utilisateur en cours d'édition en quelques étapes :
  1. Création d'un bean User avec les informations modifiées du formulaire
  2. Création d'une instance de la classe HttpPost pointant sur /user/{user.getId()}
  3. Le Content-Type du Header de la requête précise que le contenu est de type application/json
  4. Le bean User est sérialisé en JSON via la librairie Gson
  5. La représentation JSON est encodée en UTF-8 et positionnée dans la requête HTTP
  6. La requête est envoyé via une GetUserTask afin de récupérer le bean User retourné par le serveur
// mise a jour du currentUser
User updatedUser = new User();
updatedUser.setNom(name.getEditableText().toString());
updatedUser.setPrenom(forName.getEditableText().toString());
// création d'une requête de type POST
// l'URL contient l'ID du User à mettre à jour
HttpPost request = new HttpPost(
        getString(R.string.user_endpoint) + "/" + currentUser.getId());
// précision du Content-Type
request.setHeader("Content-Type", JSON_CONTENT_TYPE);
synchronized (lock) {
    try {
        // l'objet de type User sérialisé est envoyé dans le corps
        // de la requête HTTP et encodé en UTF-8 (cf. Jackson)
        request.setEntity(new StringEntity(gson.toJson(updatedUser), ENCODING_UTF_8));
    } catch (UnsupportedEncodingException e) {}
}
(new GetUserTask()).execute(request);
L'emploi de l'encodage UTF-8 est imposé par la librairie Jackson utilisé par Jersey côté serveur pour la sérialisation / désérialisation du JSON. En espérant un support de l'encodage ISO-8859-1 dans une prochaine release...

Pour l'appel aux autres méthodes du serveur vous pouvez regarder le code complet de l'Activity donné ci-dessous :
package net.androgames.blog.sample.rest.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class UserConsumer extends Activity implements OnItemClickListener {
    
    private static final DefaultHttpClient httpClient = new DefaultHttpClient();
    static {
        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setContentCharset(params, "UTF-8");
        httpClient.setParams(params); 
    }
    
    private static final Gson gson = new Gson();

    private static final String JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
    private static final String ENCODING_UTF_8 = "UTF-8";

    private static final int DIALOG_ERROR = 0;
    private static final int DIALOG_LOADING = 1;
    
    private EditText name, forName;
    private Button insertOrUpdate, initialize, delete;
    
    private User currentUser; // utilisateur courant
    private UserAdapter adapter; // utilisé comme référentiel local
    
    private Object lock = new Object();
    
    /**
     * Classe abstraite pour l'envoi de requêtes asynchrones au serveur.
     */
    private abstract class AbstractTask 
            extends AsyncTask<HttpRequestBase, Void, HttpResponse> {
        
        /**
         * Appelée avant le lancement du traitement en arrière plan.
         * Cette méthode est exécutée dans le Thread appelant.
         */
        protected void onPreExecute() {
            showDialog(DIALOG_LOADING);
        }
        
        /**
         * Traitement en arrière plan.
         * Cette méthode est exécutée dans un Thread différent
         * du Thread appelant.
         */
        protected HttpResponse doInBackground(final HttpRequestBase...requests) {
            HttpResponse response = null;
            synchronized (lock) {
                try {
                    response = httpClient.execute(requests[0]);
                } catch (Exception e) {
                    Log.e(UserConsumer.class.getSimpleName(), 
                            "Erreur d'appel au serveur", e);
                }
            }
            return response;
        }

        /**
         * Appelé après la fin du traitement en arrière plan.
         */
        protected void onPostExecute(final HttpResponse response) {
            dismissDialog(DIALOG_LOADING);
            if (response == null 
                    || !(response.getStatusLine()
                            .getStatusCode() == HttpStatus.SC_OK)) {
                showDialog(DIALOG_ERROR);
            } else {
                try {
                    handleJson(response.getEntity().getContent());
                } catch (IOException e) {
                    Log.e(UserConsumer.class.getSimpleName(), 
                            "Erreur de flux", e);
                }
            }
        }
        
        /**
         * Traitement spécifique du JSON
         * @param in Le contenu de la réponse HTTP OK
         */
        protected abstract void handleJson(final InputStream in);

    };
    
    /**
     * Récupération de la liste des User
     */
    private class ListUsersTask extends AbstractTask {

        protected void handleJson(final InputStream in) {
            final Type collectionType = new TypeToken<List<User>>(){}.getType();
            List<User> users = null;
            synchronized (lock) {
                users = gson.fromJson(new InputStreamReader(in), collectionType);
            }
            // La liste récupérée initialement est la référence
            // des User pour l'application Android
            adapter.setUsers(users);
        }

    };
    
    /**
     * Recuperation d'un utilisateur
     * déjà référencé localement
     */
    private class GetUserTask extends AbstractTask {

        protected void handleJson(final InputStream in) {
            synchronized (lock) {
                updateCurrentUser(gson.fromJson(new InputStreamReader(in), User.class));
            }
        }

    };
    
    /**
     * Récupération d'un nouvel utilisateur
     * non encore référencé localement
     */
    private class AddUserTask extends AbstractTask {

        protected void handleJson(final InputStream in) {
            synchronized (lock) {
                updateCurrentUser(gson.fromJson(new InputStreamReader(in), User.class));
            }
            adapter.addUser(currentUser);
        }

    };
    
    /**
     * Suppression d'un utilisateur
     * référencé localement
     */
    private class DeleteUserTask extends AbstractTask {

        protected void handleJson(final InputStream in) {
            User fakeUser = new User();
            try {
                fakeUser.setId(new BufferedReader(
                        new InputStreamReader(in, ENCODING_UTF_8)).readLine());
            } catch (Exception e) {}
            adapter.removeUser(fakeUser);
            updateCurrentUser(null);
        }

    };
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        adapter = new UserAdapter((ListView) findViewById(R.id.users), this);
        insertOrUpdate = (Button) findViewById(R.id.insertOrUpdate);
        initialize = (Button) findViewById(R.id.initialize);
        delete = (Button) findViewById(R.id.delete);
        name = (EditText) findViewById(R.id.name);
        forName = (EditText) findViewById(R.id.forName);
    }
    
    @Override
    public void onResume() {
        super.onResume();
        // recuperation de tous les utilisateurs
        (new ListUsersTask()).execute(new HttpGet(getString(R.string.user_endpoint)));
        // initialisation des actions
        ((ListView) findViewById(R.id.users)).setOnItemClickListener(this);
        // initialisation de l'IHM
        // currentUser peut ne pas être null
        // si l'activité a été résumée
        updateCurrentUser(currentUser);
    }
    
    /**
     * Clique sur le bouton Créer ou Enregistrer
     * @param v
     */
    public void insertOrUpdate(View v) {
        if (currentUser == null) {
            // nouvel utilisateur
            User user = new User();
            user.setNom(name.getEditableText().toString());
            user.setPrenom(forName.getEditableText().toString());
            // création d'une requête de type POST
            // l'URL contient l'ID du User Ã  mettre Ã  jour
            HttpPut request = new HttpPut(getString(R.string.user_endpoint));
            // précision du Content-Type
            request.setHeader("Content-Type", JSON_CONTENT_TYPE);
            synchronized (lock) {
                try {
                    // l'objet de type User sérialisé est envoyé dans le corps
                    // de la requête HTTP et encodé en UTF-8 (cf. Jackson)
                    request.setEntity(new StringEntity(
                            gson.toJson(user), ENCODING_UTF_8));
                } catch (UnsupportedEncodingException e) {}
            }
            (new AddUserTask()).execute(request);
        } else {
            // mise a jour du currentUser
            User updatedUser = new User();
            updatedUser.setNom(name.getEditableText().toString());
            updatedUser.setPrenom(forName.getEditableText().toString());
            // création d'une requête de type POST
            // l'URL contient l'ID du User Ã  mettre Ã  jour
            HttpPost request = new HttpPost(
                    getString(R.string.user_endpoint) + "/" + currentUser.getId());
            // précision du Content-Type
            request.setHeader("Content-Type", JSON_CONTENT_TYPE);
            synchronized (lock) {
                try {
                    // l'objet de type User sérialisé est envoyé dans le corps
                    // de la requête HTTP et encodé en UTF-8 (cf. Jackson)
                    request.setEntity(new StringEntity(
                            gson.toJson(updatedUser), ENCODING_UTF_8));
                } catch (UnsupportedEncodingException e) {}
            }
            (new GetUserTask()).execute(request);
        }
    }
    
    /**
     * Clique sur le bouton Supprimer
     * @param v
     */
    public void delete(View v) {
        // envoi d'une requête DELETE au serveur
        // sur l'URL correspondant au User Ã  supprimer
        (new DeleteUserTask()).execute(new HttpDelete(
                getString(R.string.user_endpoint) + "/" + currentUser.getId()));
    }
    
    /**
     * Clique sur le bouton Annuler
     * @param v
     */
    public void initialize(View v) {
        // raz du formulaire
        updateCurrentUser(null);
    }

    // Met a jour le formulaire avec les
    // informations de l'utilisateur passé
    // en paramètre. Si le formulaire est 
    // positionné sur les données d'un utilisateur
    // de même id, le référentiel local est mis à jour
    private void updateCurrentUser(User user) {
        if (user == null) {
            currentUser = null;
            name.setText("");
            forName.setText("");
            delete.setVisibility(View.GONE);
            initialize.setVisibility(View.GONE);
            insertOrUpdate.setText(R.string.create);
        } else {
            if (!user.equals(currentUser)) {
                // changement de User
                currentUser = user;
            } else {
                // mise a jour des informations du User
                // dans le référentiel local
                currentUser.setNom(user.getNom());
                currentUser.setPrenom(user.getPrenom());
                adapter.notifyDataSetChanged();
            }
            // mise à jour du formulaire
            name.setText(currentUser.getNom());
            forName.setText(currentUser.getPrenom());
            delete.setVisibility(View.VISIBLE);
            initialize.setVisibility(View.VISIBLE);
            insertOrUpdate.setText(R.string.update);
        }
    }
    
    @Override
    public Dialog onCreateDialog(int dialogId) {
        Dialog dialog = null;
        AlertDialog.Builder builder = null;
        switch (dialogId) {
        
            case DIALOG_LOADING : // recuperation en cours...
                dialog = new ProgressDialog(this);
                ((ProgressDialog) dialog).setIndeterminate(true);
                ((ProgressDialog) dialog).setMessage(getString(R.string.loading));
                break;
                
            case DIALOG_ERROR : // message d'erreur
                builder = new AlertDialog.Builder(this);
                builder.setTitle(R.string.error)
                       .setMessage(R.string.error_message)
                       .setNegativeButton(R.string.close, new OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                dialog = builder.create();
                break;
        }
        return dialog;
    }
    
    /**
     * L'utilisateur à cliqué sur un User de la liste,
     * le formulaire est mis à jour avec les informations
     * du User récupérés depuis le serveur
     */

    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // mise a jour du formulaire avec les informations
        // du User sélectionné par l'utilisateur
        updateCurrentUser((User) adapter.getItem(position));
        
        // on aurait également pu demander l'utilisateur 
        // au serveur à chaque fois, mais on considere
        // que la liste initialement chargée est notre
        // référence afin de garder un jeu de donnée cohérent
        
        // (new GetUserTask()).execute(new HttpGet(
        //        getString(R.string.user_endpoint) + 
        //        "/" + ((User) adapter.getItem(position)).getId()));
    }
    
}
Vous pouvez récupérer l'exemple complet sur Google Code : SampleAndroidRestClient.

5 commentaires:

  1. Excellent boulot !!!
    Il faut maintenant que je prenne le temps d'essayer ça :p

    RépondreSupprimer
  2. merci pour cette publication.c'est vraiment un excellent boulot...bonne continuation...

    RépondreSupprimer
  3. Merci beaucoup, Excellent travail ..mais je me demande si on peux utiliser le même client pour un serveur présentant le même service ( en JAX-RS biensur ) sans l'utilisation de google app engins ? :)

    RépondreSupprimer
  4. Oui, mais je vous conseillerais d'utiliser la librairie JerseyClient qui est compatible Android et permet de développer des clients de services JAX-RS simplement. D'autres librairies telles qu'AndroidAnnotations ou Spring pour Android fournissent des méthodes pour mettre en place rapidement des clients de services REST.

    RépondreSupprimer

Fork me on GitHub