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}&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