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 :
- Créer un nouvel utilisateur
- Enregistrer les modifications apportées au nom ou au prénom d'un utilisateur
- Supprimer un utilisateur
- Annuler l'édition en cours d'un utilisateur, le formulaire est alors remis à zéro
- 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 :
- Création d'un bean User avec les informations modifiées du formulaire
- Création d'une instance de la classe HttpPost pointant sur /user/{user.getId()}
- Le Content-Type du Header de la requête précise que le contenu est de type application/json
- Le bean User est sérialisé en JSON via la librairie Gson
- La représentation JSON est encodée en UTF-8 et positionnée dans la requête HTTP
- 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.