Site icon Blog Zenika

Un client Rest pour Android avec Spring-Android

Spring Android est une extension du framework Spring qui, comme son nom l’indique, vise à simplifier les développements Android. Plus précisément, il s’agit du « portage » sur Android de certains modules du framework. Âgée de seulement quelques mois, Spring-Android contient pour le moment RestTemplate et CommonsLogging.

Dans cet article, je vais présenter RestTemplate dans une application Android minimale qui interroge le service web de Google Maps pour récupérer la liste des adresses correspondant à la requête. RestTemplate, pour faire court, facilite le développement d’un client Rest.

Configuration

Commençons par installer Spring-Android. J’utilise Maven pour builder mon application, je déclare donc mes dépendances dans mon pom.xml. En plus de Spring-Android, je vais également utiliser Jackson.

<dependency>
    <groupId>org.springframework.android</groupId>
    <artifactId>spring-android-rest-template</artifactId>
    <version>1.0.0.M2</version>
</dependency>
<dependency>
	<groupId>org.codehaus.jackson</groupId>
	<artifactId>jackson-mapper-asl</artifactId>
	<version>1.7.1</version>
</dependency>
<dependency>
	<groupId>org.codehaus.jackson</groupId>
	<artifactId>jackson-core-asl</artifactId>
	<version>1.7.1</version>
</dependency>

UI

La partie UI de l’application (constituée d’une seule Activity) est très simple : un champs d’édition, un bouton et une ListView qui nous permettra d’afficher les résultats. Voici le layout

<?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">
    <EditText
    	android:id="@+id/adress"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"/>
    <Button
    	android:id="@+id/find"
    	android:text="Find"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"/>
    <ListView
    	android:id="@+id/results"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent"/>
</LinearLayout>

et la méthode onCreate de l’activity correspondante

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	find = (Button) findViewById(R.id.find);
	address = (EditText) findViewById(R.id.adress);
	listView = (ListView) findViewById(R.id.results);
	adapter = new ArrayAdapter<Address>(getApplicationContext(), android.R.layout.simple_list_item_1);
	listView.setAdapter(adapter);
	find.setOnClickListener(this);
}

Google Geocode

Le service que je vais utiliser est le service geocode de Google. Il s’interroge de la manière suivante: http://maps.googleapis.com/maps/api/geocode/{format}?address={address}&amp;sensor=false avec format à choisir parmi json et xml et address à remplir par notre requête. Voici donc la manière la plus simple d’interroger ce service.

String address = "18 rue de la monnaie";
String url = "http://maps.googleapis.com/maps/api/geocode/json?address={address}&sensor=false";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class, address);

La méthode getForObject prend en paramètres l’url du service, le type de retour – ici j’ai utilisé le type String pour obtenir la réponse Json brute, mais je vais montrer par la suite qu’on peut faire beaucoup plus acceptable – et les paramètres de l’url. Ici je n’avais qu’un seul paramètre mais on peut en mettre autant qu’on veut.

Jackson

Je vais montrer maintenant comment facilement parser le réponse Json pour obtenir un objet représentant le résultat. Notre requête nous retourne une réponse Json au format décrit dans ce document. On commence par indiquer à notre RestTemplate le convertisseur à utiliser pour les réponses Json. J’utilise ici le convertisseur Json Jackson.

restTemplate = new RestTemplate();
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
List<HttpMessageConverter<?>> listHttpMessageConverters = restTemplate.getMessageConverters();
listHttpMessageConverters.add(jsonConverter);
restTemplate.setMessageConverters(listHttpMessageConverters);

On écrit ensuite les classes de mapping.

public static class Addresses
{
	public String status;
	public Address[] results;
}
@JsonIgnoreProperties({"address_components", "types", "geometry", "partial_match"})
public static class Address
{
	public String formatted_address;
	@Override
	public String toString() {
		return formatted_address;
	}
}

Ce que nous renvoie notre service est donc un objet Addresses constitué d’un statut et d’un tableau d’objets de type Address. Il suffit de nommer les propriétés de la même manière qu’elles sont nommées en Json. Pour les adresses, je ne m’intéresse qu’à leur champ formatted_adress, j’utilise donc l’annotation @JsonIgnoreProperties pour lister toutes les propriétés qui ne m’intéressent pas. J’en profite pour redéfinir toString() en vue de l’affichage ultérieur. Il ne me reste plus qu’à demander à mon RestTemplate de m’obtenir un objet Addresses.

Addresses addresses = restTemplate.getForObject(URL, Addresses.class, address);

Requête Asynchrone

Il est temps maintenant d’insérer notre RestTemplate dans notre Activity. J’appelle le RestTemplate au clic sur le bouton. Mais pour ne pas figer l’UI, j’ai utilisé une AsyncTask.

public void onClick(View view) {
	if(view.getId() == R.id.find){
		CharSequence csAddress = address.getText();
		if(!TextUtils.isEmpty(csAddress)){
			new GetRestTask().execute(csAddress);
		}
	}
}
private class GetRestTask extends AsyncTask<CharSequence, Void, Addresses>
{
	@Override
	protected Addresses doInBackground(CharSequence... address) {
		if(address != null && address.length > 0)
		{
			try
			{
				return getRestTemplate().getForObject(URL, Addresses.class, address[0]);
			}
			catch (RestClientException e) {
				return null;
			}
		}
		return null;
	}
	@Override
	protected void onPostExecute(Addresses addresses) {
		adapter.clear();
		if(addresses == null || addresses.status.equals(NO_RESULTS))
		{
			Toast.makeText(getApplicationContext(), "Non trouvée ou erreur", Toast.LENGTH_SHORT).show();
		}
		else
		{
			Log.d(TAG, "onPostExecute : "+addresses.status);
			for(Address addr : addresses.results)
			{
				adapter.add(addr);
			}
		}
	}
	private RestTemplate getRestTemplate() {
		if(restTemplate == null)
		{
			restTemplate = new RestTemplate();
			[...]
		}
                return restTemplate;
       }
}

Résultat

Les sources de l’application sont disponibles sur le Github de Zenika

Quitter la version mobile