Map application pour Android SDK 1.5, deuxième partie

André vous proposait récemment de découvrir les fonctionnalités maps offertes par la version 1.5 du SDK Android, voici la suite de ce tutoriel. La troisième partie est déjà en route !

Mon premier tutoriel présentait les bases d’une application Android et Google Maps élémentaire.

Ce deuxième tutoriel va présenter les notions de map overlay, de géolocalisation et de client http. Pour cela nous présenterons une solution de recherche de services basées sur Yahoo Search API et par la même occasion cela donnera un exemple d’utilisation de REST API avec Android.

NB : Yahoo Search ne fonctionne qu’aux US pour l’instant, placez donc votre emulateur au dessus des US avec un geo fix approprié.

La license de Yahoo Search ne permet pas son utilisation a des fins commerciales.

Dans un prochain tutoriel nous utiliserons Google Search et n’aurons pas ces limitations.

Pour la réalisation du client REST (Representational State Transfer) nous utiliserons la librairie HttpClient. Dans GLocation j’utilisais android.net.http.RequestQueue mais cette API, bien que toujours utilisée en interne par Android, n’est plus publique. Pour la recherche de services j’utilisais une solution un peu complexe faisant appel a Google Search, mais les restrictions de droits d’usage en interdisent plus ou moins l’usage aujourd’hui..

Maps Overlay avec Android SDK 1.5

Apres avoir essayé le nouvel Overlay proposé dans la librairie MyLocationOverlay je l’ai abandonné faute de pouvoir gérer la fréquence de rafraîchissement et donc de pouvoir limiter l’usage du GPS. J’ai ensuite essayé de faire un custom Overlay pour enfin choisir l’ItemizedOverlay qui correspond bien pour gérer les résultats de la recherche.

Description de l’Itemized Overlay

L’ItemizedOverlay est un Overlay qui permet d’ajouter des OverlayItems et ensuite de les sélectionner.

Nous allons d’abord créer une classe Placemark, extension de la class OverlayItem qui définissent les Items contenus dans l’Overlay.

Chaque Object Placemark contiendra les informations de chaque point résultant de la recherche avec des accesseurs pour les extraire si nécessaire.

Cela permettra de récuperer toutes les propriétes venant des résultats des recherches et des les transmettre avec la position, le titre, la description a l’overlay en une fois.

Placemark.java :

package org.example.android.apis;
 
import android.os.Bundle;
 
import com.google.android.maps.GeoPoint;
import com.google.android.maps.OverlayItem;
import com.google.map.MapPoint;
 
/**
* Placemark Object containing
* all informations about our search results
* Definition des parametres :
* http://developer.yahoo.com/search/local/V3/localSearch.html
*
* @author Andre Charles Legendre
*
*/
 
public class Placemark extends OverlayItem {
String mTitle;
String mSnippet;
GeoPoint mPoint;
 
private Bundle placeBundle;
 
public Placemark(GeoPoint point, String title, String snippet,
Bundle placeBundle) {
super(point, title, snippet);
this.placeBundle = placeBundle;
this.mTitle = title;
this.mSnippet = snippet;
this.mPoint = point;
}
 
public Bundle getBundle() {
return this.placeBundle;
}
 
public String getid() {
return placeBundle.getString("id");
}
 
public String getAddress() {
return placeBundle.getString("Address");
}
 
public String getCity() {
return placeBundle.getString("City");
}
 
public String getState() {
return placeBundle.getString("State");
}
 
public String getPhone() {
return placeBundle.getString("Phone");
}
 
public String getLatitude() {
return placeBundle.getString("Latitude");
}
 
public String getLongitude() {
return placeBundle.getString("Longitude");
}
 
public String getRating() {
return placeBundle.getString("Rating");
}
 
public String getAverageRating() {
return placeBundle.getString("AverageRating");
}
 
public String getTotalRatings() {
return placeBundle.getString("TotalRatings");
}
 
public String getTotalReviews() {
return placeBundle.getString("TotalReviews");
}
 
public String getLastReviewDate() {
return placeBundle.getString("ReviewDate");
}
 
public String getLastReviewIntro() {
return placeBundle.getString("ReviewIntro");
}
 
public String getDistance() {
return placeBundle.getString("Distance");
}
 
public String getUrl() {
return placeBundle.getString("Url");
}
 
public String getClickUrl() {
return placeBundle.getString("ClickUrl");
}
 
public String getMapUrl() {
return placeBundle.getString("MapUrl");
}
 
public String getBusinessUrl() {
return placeBundle.getString("BusinessUrl");
}
 
public String getBusinessClickUrl() {
return placeBundle.getString("BusinessClickUrl");
}
 
public Bundle getCategories() {
return placeBundle.getBundle("Categories");
}
 
public MapPoint getLocation() {
return new MapPoint(Integer.decode(placeBundle.
getString("Latitude")),Integer.decode(placeBundle.
getString("Longitude")));
}
 
public String toString() {
return (placeBundle.toString());
}
}

Nous allons ensuite créer une classe MapDemoOverlay qui étend ItemizedOverlay.

Les markers des Items sont affichés automatiquement par la classe ItemizedOverlay, pour ce faire on les passe en paramètre à la création de l’Overlay.

La methode clé de cette classe est la methode draw. La methode draw n’a besoin d’afficher que ce qui concerne les titres et autres Toasts ou InfoWindows.

Une des différence majeure concernant la methode draw en 1.5 est l’utilisation de l’objet Projection pour trouver les coordonnées écran à partir des coordonnées geographiques.

La méthode onTap est très simple du fait que onTap de ItemizedOverlay recupère directement l’index de l’item selectionné.

De ce fait en utilisant l’index onTap peut directement récupérer un objet Placemark et ainsi toutes les infos fournis par Yahoo Search.

Place au code pour plus de clarté :

/**
* This method is they key element of our map application
* ...
*/
protected class MapDemoOverlay extends ItemizedOverlay<OverlayItem> {
private List<OverlayItem> items = new ArrayList<OverlayItem>();
 
public MapDemoOverlay(Drawable marker) {
super(boundCenterBottom(marker));
populate();
}
 
private Paint innerPaint, borderPaint, textPaint;
 
@Override
public boolean onTap(int index) {
Placemark placeMark = (Placemark) myLocationOverlay.getItem(index);
Log.e("MapViewDemo", "ONTAP index : " + index + " place : "
+ placeMark.getTitle());
return super.onTap(index);
}
 
@Override
public void draw(Canvas canvas, MapView myMapView, boolean shadow) {
super.draw(canvas, myMapView, shadow);
// Log.e("MapViewDemo", "MapDemoOverlay...draw");
// Setup our "brush"/"pencil"/ whatever...
Paint paint = new Paint();
paint.setTextSize(12);
 
// Create a Point that represents our GPS-Location
Double lat = MapViewDemo.this.myLocation.getLatitude() * 1E6;
Double lng = MapViewDemo.this.myLocation.getLongitude() * 1E6;
GeoPoint geoPoint = new GeoPoint(lat.intValue(), lng.intValue());
Projection myprojection = myMapView.getProjection();
Point point = new Point();
myprojection.toPixels(geoPoint, point);
// Setup a color for our location
paint.setStyle(Style.STROKE);
paint.setARGB(255, 80, 150, 30); // Nice strong Android-Green
// Draw our name
canvas.drawText(getString(R.string.map_overlay_own_name),
point.x + 9, point.y, paint);
paint.setStrokeWidth(1);
paint.setARGB(255, 0, 0, 0);
canvas.drawCircle(point.x, point.y, 7, paint);
// Log.e("MapViewDemo", "MapDemoOverlay...draw
paint.setStyle(Style.FILL);
drawInfoWindow(canvas, myprojection, shadow);
ArrayList<Placemark> placeMarks = MapViewDemo.this.getSearch();
Placemark placeMark;
if (placeMarks != null)
synchronized (placeMarks) {
for (int it = 0; it < placeMarks.size(); it++) {
placeMark = (Placemark) placeMarks.get(it);
geoPoint = placeMark.getPoint();
myprojection.toPixels(geoPoint, point);
canvas.drawText(placeMark.getTitle(), point.x + 9,
point.y, paint);
}
}
}
 
public void addItem(OverlayItem item) {
items.add(item);
populate();
}
 
@Override
protected OverlayItem createItem(int index) {
return (OverlayItem) items.get(index);
}
 
@Override
public int size() {
return items.size();
}
 
private void drawInfoWindow(Canvas canvas, Projection myprojection,
boolean shadow) {
 
if (MapViewDemo.this.selectedSearchPoint != null) {
GeoPoint geoPoint = MapViewDemo.this.selectedSearchPoint
.getPoint();
Point point = new Point();
myprojection.toPixels(geoPoint, point);
 
// Setup the info window with the right size & location
int INFO_WINDOW_WIDTH = 150;
int INFO_WINDOW_HEIGHT = 30;
RectF infoWindowRect = new RectF(0, 0, INFO_WINDOW_WIDTH,
INFO_WINDOW_HEIGHT);
int infoWindowOffsetX = point.x - INFO_WINDOW_WIDTH / 2;
int infoWindowOffsetY = point.y - INFO_WINDOW_HEIGHT
- bubbleIcon.getHeight();
infoWindowRect.offset(infoWindowOffsetX, infoWindowOffsetY);
 
// Draw inner info window
canvas.drawRoundRect(infoWindowRect, 5, 5, getInnerPaint());
// Draw border for info window
canvas.drawRoundRect(infoWindowRect, 5, 5, getBorderPaint());
// Draw the MapLocation's name
int TEXT_OFFSET_X = 10;
int TEXT_OFFSET_Y = 15;
Log.e("Glocation", " selectedSearchPoint5 "
+ MapViewDemo.this.selectedSearchPoint);
canvas.drawText(MapViewDemo.this.selectedSearchPoint
.getSnippet(), infoWindowOffsetX + TEXT_OFFSET_X,
infoWindowOffsetY + TEXT_OFFSET_Y, getTextPaint());
}
}
 
public Paint getInnerPaint() {
if (innerPaint == null) {
innerPaint = new Paint();
innerPaint.setARGB(225, 75, 75, 75); // gray
innerPaint.setAntiAlias(true);
}
return innerPaint;
}
 
public Paint getBorderPaint() {
if (borderPaint == null) {
borderPaint = new Paint();
borderPaint.setARGB(255, 255, 255, 255);
borderPaint.setAntiAlias(true);
borderPaint.setStyle(Style.STROKE);
borderPaint.setStrokeWidth(2);
}
return borderPaint;
}
 
public Paint getTextPaint() {
if (textPaint == null) {
textPaint = new Paint();
textPaint.setARGB(255, 255, 255, 255);
textPaint.setAntiAlias(true);
}
return textPaint;
}
}

Détection des changements de sélection

Lors de la description de la class MapDemoOverlay nous avons présenté la method onTap retournant le numéro de l’item sélectionné.

Cette méthode ne permet pas de détecter facilement les changements de sélection.

Pour le faire nous allons ajouter à notre Overlay un listener ItemizedOverlay.OnFocusChangeListener .

Ce listener est affecté à notre Overlay par l’appel :

myLocationOverlay.setOnFocusChangeListener(FOCUS_LISTENER); où FOCUS_LISTENER est notre OnFocusChangeListener.

Dans notre cas précis le listener tiendra à jour MapViewDemo.this.selectedSearchText utilisé par draw pour afficher le contenu de infoWindow donnant des informations au sujet de l’item sélectionné par l’utilisateur.

ItemizedOverlay.OnFocusChangeListener FOCUS_LISTENER = new ItemizedOverlay.OnFocusChangeListener() {
public void onFocusChanged(ItemizedOverlay overlay, OverlayItem newFocus) {
if (newFocus != null) {
Log.e("MapViewDemo", "MapViewDemo ChangeFocus "
+ newFocus.getTitle());
}
MapViewDemo.this.selectedSearchPoint = (Placemark) newFocus;
}
};
}

Description de la gestion de la géolocalisation

En utilisant la géolocalisation nous pouvons obtenir notre position immédiate.

Nous pouvons aussi créer un LocationListener en charge de gérer nos changements de position.

Pour simplifier le code nous avons groupé cela dans une méthode initMyLocation() appellée au démarrage, dans la méthode onCreate.

initMyLocation affiche d’abord notre position courante en appelant myLocationManager.getLastKnownLocation et initialise le rafraîchissement de notre position quand on se deplace en créant un LocationListener et en le passant en paramètre a this.myLocationManager.requestLocationUpdates.

Cette solution nous permet de configurer la fréquence des mises à jour de notre position à l’aide des 2 paramètres :

MINIMUM_TIME_BETWEEN_UPDATE, MINIMUM_DISTANCECHANGE_FOR_UPDATE

initMyLocation initialise aussi l’overlay dans lequel seront affichés notre geolocalisation et les résultats de nos recherches :

/**
* Initialises the MyLocationOverlay and adds it to the overlays of the map
* Prepare the things, that will give us the ability, to receive
* Information about our GPS-Position.
*/
private void initMyLocation() {
myLocationOverlay = new MapDemoOverlay(marker);
myLocationOverlay.setOnFocusChangeListener(FOCUS_LISTENER);
myMapView.getOverlays().add(myLocationOverlay);
this.myLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
this.myLocation = myLocationManager.getLastKnownLocation("gps");
Double lat = this.myLocation.getLatitude() * 1E6;
Double lng = this.myLocation.getLongitude() * 1E6;
GeoPoint geoPoint = new GeoPoint(lat.intValue(), lng.intValue());
this.myMapController.setCenter(geoPoint);
this.myMapController.setZoom(11);
myMapView.setClickable(true);
myMapView.setEnabled(true);
/*
* Register with out LocationManager to send us an intent (whos
* Action-String we defined above) when an intent to the location
* manager, that we want to get informed on changes to our own position.
*/
locListener = new LocationListener() {
 
public void onLocationChanged(Location newLocation) {
if (System.currentTimeMillis() > MapViewDemo.this.endTime) {
MapViewDemo.this.endTime = System.currentTimeMillis()
+ MINIMUM_TIME_BETWEEN_UPDATE_LIST;
}
if (MapViewDemo.this.doUpdates)
MapViewDemo.this.updateView(newLocation);
}
 
public void onProviderDisabled(String provider) {
}
 
public void onProviderEnabled(String provider) {
}

Utilisation de HttpClient pour appeler Yahoo Search

La version du code est une version simpliste où l’application attend la réponse et son formattage.

Une version utilisable en production supposerait de créer un thread pour gérer ces traitements en background.

J’ai préféré simplifier pour garder la lisibilité.

Android propose deux libraries pour les connection http (3 si on compte les sockets).

La premiere est android.net et la second org.apache.http.client.HttpClient .

J’utilise cette dernière car j’ai déjà eu a l’utiliser par ailleurs et que c’est un outil très stable et  efficace.

Pour faciliter la réutilisation de ce code j’ai créé un objet MapDemoConnection en charge de gérer la connection http avec le service Yahoo Search.

Cet objet construit l’addresse url, l’appelle, recupère les données et lance le parsing.

MapDemoConnection.java :

package org.example.android.apis;import java.io.IOException;
 
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.xml.sax.SAXException;
 
import android.util.Log;
import android.util.Xml;
 
/**
* MapDemoConnection sends http requests to the web service.
*
* @author Andre Charles Legendre
*
*/
 
public class MapDemoConnection {
 
private static String USER_AGENT = "Mozilla/4.5";
private static String TAG = "MapDemoConnection";
 
private String password;
private String serverUrl;
 
public MapDemoConnection(String serverUrl, String username, String password) {
this.serverUrl = serverUrl;
this.password = password;
}
 
/**
* Call Service and parse results
*
* @param resource
*            String containing text you want to search with
* @param resourceType
*            String GET or POST
* @param httpParams
*            List<NameValuePair> List of parameters send to the service
* @param myContentHandler
*            MyContentHandler Object in charge to parse the result
* @return ArrayList<Placemark> containing List of PlaceMark one for each
*         result of the search
*/
 
public ArrayList<Placemark> parse(String resource, String resourceType,
List<NameValuePair> httpParams, MyContentHandler myContentHandler) {
if (resource.length() > 140) {
throw new IllegalArgumentException(
"Search text is longer than 140 characters");
}
String xml = null;
try {
if (resourceType.equals("GET")) {
xml = getRequest(resource);
} else {
xml = postRequest(resource,
new UrlEncodedFormEntity(httpParams));
}
} catch (SocketTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (xml != null) {
try {
Log.e("MyEventHandler", "xml=" + xml);
Xml.parse(xml, myContentHandler);
} catch (SAXException e) {
Log.e("MyEventHandler", "xml=" + xml + "ERROR=" + e.toString());
}
return myContentHandler.parse();
}
return null;
}
 
/**
* Sends a GET request to web service
*
* @param resource
* @return String response from the server
*/
private String getRequest(String resource) {
String result = null;
String url = serverUrl + resource;
Log.d(TAG, "GET: " + url);
 
HttpClient client = new DefaultHttpClient(new BasicHttpParams());
HttpGet getMethod = new HttpGet((url));
try {
getMethod.setHeader("User-Agent", USER_AGENT);
getMethod.addHeader("Authorization", "Basic " + getCredentials());
 
HttpResponse httpResponse = client.execute(getMethod);
result = retrieveInputStream(httpResponse.getEntity());
 
} catch (Exception e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
} finally {
getMethod.abort();
}
Log.e(TAG, result);
return result;
}
 
/**
* Sends a POST request
*
* @param resource
* @param formParams
*            UrlEncodedFormEntity which is comprised of a List of
*            NameValuePairs
* @return String response from the server
* @throws SocketTimeoutException
*             , SocketException
*/
private String postRequest(String resource, UrlEncodedFormEntity formParams)
throws SocketTimeoutException, SocketException {
String result = null;
String url = serverUrl + resource;
Log.d(TAG, "POST: " + url);
 
HttpClient client = new DefaultHttpClient(new BasicHttpParams());
HttpPost postMethod = new HttpPost((url));
try {
// postMethod.setHeader("User-Agent", USER_AGENT);
// postMethod.addHeader("Authorization", "Basic " +
// getCredentials());
 
if (formParams != null)
postMethod.setEntity(formParams);
 
HttpResponse httpResponse = client.execute(postMethod);
result = EntityUtils.toString(httpResponse.getEntity());
 
if (result != null)
Log.d(TAG, "Returned response [" + httpResponse.getStatusLine()
+ "] from the server: " + result);
 
} catch (Exception e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
} finally {
postMethod.abort();
}
return result;
}
 
/**
* Retrieves the content input stream from a GET request
*
* @param httpEntity
* @return String service response
*/
private String retrieveInputStream(final HttpEntity httpEntity) {
int length = (int) httpEntity.getContentLength();
StringBuffer stringBuffer = new StringBuffer(length);
try {
InputStreamReader inputStreamReader = new InputStreamReader(
httpEntity.getContent(), HTTP.UTF_8);
char buffer[] = new char[length];
int count;
 
while ((count = inputStreamReader.read(buffer, 0, length - 1)) > 0) {
stringBuffer.append(buffer, 0, count);
}
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
} catch (IllegalStateException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
return stringBuffer.toString();
}
 
/**
* Retrieves the credentials
*
* @return String password
*/
private String getCredentials() {
return password;
// return new String(Base64.encode((username + ":" +
// password).getBytes()));
}
}

Parsing des données reçues de Yahoo Search

searchmaps

Pour le parsing nous allons créer un object MyContentHandler en charge de mettre en forme les données reçues.

Ces données reçues seront mises dans un ArrayList d’objects PlaceMark.

La fonction draw de l’ItemizedOveraly sera en charge d’afficher ces PlaceMark et de gérer leur interactivité.

Nous utilisons le package de org.xml.sax pour le parsing.

MyContentHandler extends DefaultHandler et doit donc implémenter toutes les méthodes startElement, endElement, etc.

En fait MapDemoConnection lance le parsing en appelant Xml.parse(xml, myContentHandler) où le paramètre xml contient les données reçues et où myContentHandler est l’objet en charge de les parser.

Lors du parsing myContentHandler appelle les méthodes startElement, endElement, etc. quand il reçoit une notification à chaque étape de la lecture du document.

MyContentHandler

package org.example.android.apis;import java.util.ArrayList;
 
import java.util.Iterator;
import java.util.Set;
 
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
 
import com.google.android.maps.GeoPoint;
 
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
 
/**
* MapViewDemo is the Map Activity of our application
*
* @author Andre Charles Legendre
*
*/
 
public class MyContentHandler extends DefaultHandler {
StringBuilder elementBuilder = null;
private String id;
private int numCat;
private Bundle bundle;
private Bundle categories;
private Bundle bundles;
private String title;
private String snippet;
 
public MyContentHandler(Activity activity) {
}
 
public ArrayList<Placemark> parse() {
return getPlaceMarks(this.bundles);
}
 
@Override
public void startDocument() throws SAXException {
// Log.e("MapDemoMYCONTENTHANDLER", "STARTDOCUMENT");
this.bundles = new Bundle();
}
 
public void endDocument() throws SAXException {
Log.e("MapDemoMYCONTENTHANDLER", "Finish");
}
 
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
// Log.e("MapDemoMYCONTENTHANDLER", "STARTELEMENT " + localName);
elementBuilder = new StringBuilder();
if (localName.equals("Categories")) {
this.numCat = 0;
categories = new Bundle();
} else if (localName.equals("Result")) {
id = atts.getValue("id");
bundle = new Bundle();
}
}
 
@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
String text = elementBuilder.toString();
//Log.e("MapDemoMYCONTENTHANDLER", "ENDELEMENT " + localName + " : "
//            + text);
if (localName.equals("ResultSetMapUrl")) {
} else if (localName.equals("Result")) {
bundles.putBundle(id, bundle);
} else if (localName.equals("Categories")) {
bundle.putBundle(id, categories);
} else if (localName.equals("Category")) {
categories.putString("" + numCat, text);
} else {
bundle.putString(localName, text);
}
}
 
@Override
public void characters(char[] chars, int i, int i1) throws SAXException {
if (elementBuilder == null) {
Log.e("MapDemoMYCONTENTHANDLER", "elementBuilder NULL");
elementBuilder = new StringBuilder();
}
elementBuilder.append(chars, i, i1);
}
 
/**
* Parse results
*
* @param mSearch
*            Bundle containing results data
* @return ArrayList<Placemark> containing List of PlaceMark one for each
*         result of the search
* @throws IOException
*/
 
private ArrayList<Placemark> getPlaceMarks(Bundle mSearch) {
ArrayList<Placemark> placeMarks = new ArrayList<Placemark>();
if (mSearch != null)
synchronized (mSearch) {
Bundle placeBundles = mSearch;
Set<String> places = placeBundles.keySet();
Placemark placeMark = null;
Bundle Categories;
String Latitude = "0.0000";
String Longitude = "0.0000";
Double lat;
Double lng;
GeoPoint geoPoint;
String fieldName;
Bundle placeBundle;
Set<String> placeFields;
Set<String> categories;
 
for (Iterator<String> it = places.iterator(); it.hasNext();) {
placeBundle = placeBundles.getBundle(it.next());
placeFields = placeBundle.keySet();
for (Iterator<String> iter = placeFields.iterator(); iter
.hasNext();) {
fieldName = iter.next();
if (fieldName.equals("Categories")) {
Categories = placeBundle.getBundle(fieldName);
categories = Categories.keySet();
for (Iterator<String> itr = categories.iterator(); itr
.hasNext();) {
// TODO utiliser les categories
@SuppressWarnings("unused")
String catName = Categories.getString(itr
.next());
}
} else if (fieldName.equals("Latitude")) {
Latitude = placeBundle.getString(fieldName);
} else if (fieldName.equals("Longitude")) {
Longitude = placeBundle.getString(fieldName);
} else if (fieldName.equals("Title")) {
title = placeBundle.getString(fieldName);
} else if (fieldName.equals("LastReviewIntro")) {
snippet = placeBundle.getString(fieldName);
}
}
// Placemark placemark = search.getPlacemark(i);
// Log.e("GlocationMAP", "MyLocationOverlay...draw
// Search " + i);
Log.e("GlocationMAP", "MyLocationOverlay...snippet "
+ snippet);
lat = Double.valueOf(Latitude) * 1E6;
lng = Double.valueOf(Longitude) * 1E6;
geoPoint = new GeoPoint(lat.intValue(), lng.intValue());
placeMark = new Placemark(geoPoint, title, snippet,
placeBundle);
// placeMark.setMarker(marker);
placeMarks.add(placeMark);
}
}
return placeMarks;
}
}

Ecran de saisie de la requête utilisateur de recherche

Nous allons créer une nouvelle activité SearchActivity pour permettre à l’utilisateur de saisir une recherche.

SearchActivity doit créer un espace de saise du texte et un bouton pour lancer la recherche.

La syntaxe des recherches que l’utilisateur doir entrer est celle de Yahoo Search.

Votre location doit être a l’intérieur des Etats-Unis du à la limitation de Yahoo Search, autrement vous n’aurez aucun résultat.

On limite la longueur a 140 caractères.

SearchActivity.java

package org.example.android.apis;import android.app.Activity;
 
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.view.View;
 
/**
* SearchActivity Activity getting search from user sending result by an intent
*
* @author Andre Charles Legendre
*
*/
 
public class SearchActivity extends Activity {
protected static final String ACTIVITY_RESULT = "org.example.android.apis.EDIT";
 
public SearchActivity() {
}
 
/**
* Called with the activity is first created.
*/
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
 
// Set the layout for this activity.
setContentView(R.layout.search);
 
Button addButton = (Button) findViewById(R.id.searchButton);
addButton.setOnClickListener(new View.OnClickListener() {
 
public void onClick(View v) {
Intent i = new Intent();
// We limit length to 140 to avoid exception in
// MapDemoConnection
String result = ((EditText) findViewById(R.id.searchText)).getText().toString();
if (result.length() > 140)
result = result.substring(0, 139);
i.putExtra(ACTIVITY_RESULT,result);
setResult(RESULT_OK, i);
finish();
}
});
 
}
}

On doit bien entendu déclarer cette Activity dans le fichier AndroidManifest.xml pour pouvoir l’utiliser.

Nous avons par ailleurs ajouté dans MapViewDemo.java une option au menu et un KeyEvent pour KEY_F pour lancer SearchActivity :

<?xml version=“1.0″ encoding=“utf-8″?>
<manifest xmlns:android=http://schemas.android.com/apk/res/android package=“org.example.android.apis”>
<permission xmlns:android=http://schemas.android.com/apk/res/android android:name=“android.permission.INTERNET”></permission>
<uses-permission android:name=“android.permission.INTERNET” />
<uses-permission android:name=“android.permission.ACCESS_FINE_LOCATION” />
<application android:label=“@string/app_name”
android:icon=“@drawable/icon” android:name=“MapDemoApplication”>
<uses-library android:name=“com.google.android.maps” />
<activity android:name=“.MapViewDemo” android:label=“@string/app_name”>
<intent-filter>
<action android:name=“android.intent.action.MAIN” />
<category android:name=“android.intent.category.DEFAULT” />
<category android:name=“android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<activity android:name=“.SearchActivity” android:label=“Find”>
<intent-filter>
<action android:name=“android.intent.action.MAIN”/>
<category android:name=“android.intent.category.GLOCATION”/>
<action android:name=“android.intent.action.SEARCH” />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion=“3″ />
</manifest>

Conclusion

Nous voilà au bout de ce tutorials qui nous a permis de voir beaucoup de nouveaux éléments.

Il y en aura encore dans la suite, nous verrons ce que ça donne avec Google Search.

Nous aborderons également le composant WebView et les interactions possible avec Java et  JavaScript. Attention ça va en surprendre plus d’un…

J’attends vos remarques, critiques, propositions d’amelioration.

Télécharger les sources

Pour aller plus loin :

40 commentaires sur "Map application pour Android SDK 1.5, deuxième partie" :

  • Romain Guy le 1/06/2009

    Ooh c’est mal, tu fais plein d’allocations d’objets dans les méthodes de dessin. Ça va forcer le GC à faire son boulot et ça va donc ralentir les scrollings, etc. :)

  • Andre Charles Legendre le 1/06/2009

    Bonjour Romain.

    Tu as raison, ce code est loin d’etre optimise.
    Mais ca montre comment utiliser les ItemizedOverlay et comment faire des appels REST avec du parsing xml.
    Note que dans le prochain tutorial le search se fera avec Google.
    Comme quoi je ne suis pas foncierement mauvais….

    A+

    Andre

  • Romain Guy le 1/06/2009

    Je ne dis pas que tu es mauvais, mais des articles comme celui-ci se doivent de montrer le bon exemple. Et si toi tu sais qu’il y a des problèmes, ce n’est pas forcément le cas des lecteurs qui vont recréer les même erreurs. J’ai vu cela arriver beaucoup trop de fois dans le cas de Swing pour ne pas réagir :)

  • Andre Charles Legendre le 1/06/2009

    Peux tu donner des details sur les problemes et sur les solutions que tu preconise. Je te promet d’inclure les corrections dans le prochain tutorial.
    En fait n’ayant pas encore le bonheur d’avoir un tel Android (Eh oui je suis a Chypre). Je n’ai qu’une notion vague des vitesses d’excutions.
    Mai j’ai le soleil…

  • K20 le 3/06/2009

    Il manque pas mal de morceaux de code non ?
    Si on suit à la lettre la 1ere partie http://www.pointgphone.com/map.....dk-15-2597 il y a de nombreuses variables qui ne sont pas référencés :s

  • Andre Charles Legendre le 3/06/2009

    Bonjour K20
    Le code a l’interieur du tutorial est incomplet.
    J’ai fourni a Loic un repertoire complet du projet eclipse de ce tutorial.
    Si Loic ne peut pas le mettre en ligne, je le transmettrai a ceux qui le demanderont.

  • K20 le 3/06/2009

    ça m’intéresse beaucoup :)
    Tu as mon email donc si tu peux me l’envoyer ça m’arrangerais ;)

  • Andre Charles Legendre le 3/06/2009

    C parti

    A+

  • Mat le 5/06/2009

    Bonjour,
    je suis aussi intéressé par le code complet de l’exemple si c’est possible,

    Merci d’avance.

  • Mat le 5/06/2009

    Merci pour le mail.
    J’ai une question, serait il possible d’ajouter un listener , onclick par exemple sur les “bubles” afin de lancer une activity lorsque l’utilisateur clique dessus ?

  • Andre Charles Legendre le 5/06/2009

    J’ai envoye le code et pour ce qui est d’un listener pour les click qui m’a ete demande en fait vous avez la methode onTab dans le ItemizedOverlay

    suivez le code..

  • Anthonny Quérouil le 5/06/2009

    pour ceux qui utilise la version r2 du sdk 1.5, la classe MapPoint n’existe plus.

    Un contournement a été proposé en remplaçant la librairie map.jar :
    http://groups.google.com/group.....40d21be264

  • Andre Charles Legendre le 5/06/2009

    OUi c’est vrai, dans le tutorial 3 j’utilise la r2.

    Si ca te bloque vraiment.
    Dis le moi et j’essaierai de poster demain soir une petite explication en attendant que Loic ai le temps de publier le 3eme tuto.

  • Anthonny Quérouil le 5/06/2009

    J’ai réglé ce détail en conservant le maps.jar du SDK 1.5_r2 en utilisant la classe GeoPoint (qui apparemment remplace MapPoint) dans la classe Placemark.

  • raandria le 8/06/2009

    Bonjour,

    C’est génial les tutos comme ça. ça m’intéresserai aussi d’avoir le code complet de l’exemple stp :D

    Merci!

  • Andre Charles Legendre le 8/06/2009

    Ca y est raandria, je viens de te les envoyer.

    Andre

  • raandria le 8/06/2009

    Merci Andre!
    On attends avec impatience la suite avec google search

  • aoulili le 10/06/2009

    Bonjour Andre!
    je viens de commencer mes developpement sous la platform android, et je suis bien interessé par ton exemple!!

    est ce que tu peux me l’envoyer stp!! ;-)
    merci

  • Abir Saïd le 12/06/2009

    Merci Andre pour tous ces tutoriaux.

    Je suis vraiment intéressée par ce tuto, pouvez-vous m’envoyer le code source SVP.

    Merci d’avance.

  • aoulili le 12/06/2009

    Bonjour Andre!
    merci pour tous ces tutoriaux. est ce que c’est possible que vous m’envoyez le code source de cette application!?
    merci d’avance!
    aliwa

  • Andre Charles Legendre le 12/06/2009

    Ca y est je vous les ai envoye a tous les 2

    Bon courage.

    Andre

  • Yorick le 19/06/2009

    Bonjour Andre,

    vraiment bien fait vos tuto, pourriez vous m`envoyer les sources de cette application?

    Merci

    Yorick

  • Eden le 19/06/2009

    Salut André,

    C’est Clément !
    Peux – tu me filer les sources aussi, ça m’intéresse.

    Merci

  • Andre Charles Legendre le 19/06/2009

    Yorik et Clement je viens de vous envoyer les sources sur votre e_mail comme promis

    Andre

  • Benjamin le 24/06/2009

    Bonjour André,

    Ce tuto est vraiment intéressant, pourriez vous m’envoyer les sources de cette application.
    Merci d’avance

  • Nicolas le 25/06/2009

    Bonjour André,

    Je rejoins l’avis général, excellent tuto.
    La gestion des overlay à l’air très poussée !

    Si vous pouviez également m’envoyer les sources, ce serait super.

    Merci beaucoup.

    Nicolas.

  • Andre le 28/06/2009

    Benjamin et Nicolas

    Pour que je puisse poster les sources
    J’ai besoin de votre e_mail
    Je pense que si vous vous enregistreza au niveau de pointgphone en reseignant votre e_mail
    Ca irait.
    Postez moi un mail directement sur ma boite et je vous repondrai
    Meme consigne a tous ceux qui ne l’aurai pas recu.

    Andre

  • PointGPhone le 28/06/2009

    Les sources du tuto sont maintenant disponibles en téléchargement grâce au lien en fin de page.

  • Seb le 29/06/2009

    Bonjour Andre,

    Super tuto! Pourriez-vous m’envoyer les sources?

    Merci d’avance

    Seb

  • Seb le 29/06/2009

    lol
    Je n’avais pas vu le post de Loïc!!

    Encore bravo pour ce super tutoriel!

  • sweet le 8/07/2009

    Salut tout d’abord un grand bravo pour ce tuto .
    En attendant le 3ème je pourrais avoir le source stp ?
    Merci d’avance

  • sweet le 8/07/2009

    J’avais pas vu le lien dsl ^^

  • midoub le 10/08/2009

    bonjour a vous tous,
    quelle est la difference ente J2ME et Android??
    cad qu’est ce que Android peut supporter et J2ME ne peux pas supporter est vise versa???
    Merci pour votre aides.

  • albourahh le 16/09/2009

    Voila un tuto trés interessant
    En attendant le 3ème je pourrais avoir le source stp ?histoire de l’adapter
    Merci d’avance

  • Gens le 13/10/2009

    Super Tuto.
    Andre, est ce que vous pourriez m’envoyer les sources, svp?
    Merci, à bientot

  • Titi le 4/12/2009

    Bonjour Charles , bravo pour ce tuto.
    Pourrais tu m’envoyer ton code stp derniere version.
    Je dois réaliser une appli du meme type mais je débute dans la programmation android.
    Merci d’avance. A bientot
    mail :thibault.darcel@isen.fr

  • Medi le 4/01/2010

    Bonjours Charles;
    Bravo 1e autre fois pr ce tuto
    stp tu peux m’envoyer le code source
    voila mon Mail : medi_mh@hotmail.com
    et Merci*

  • y0up159 le 6/01/2010

    j’ai réussi à lancer ton projet avec eclipse depuis ubuntu mais il plante à l’exécution :/

    dans le debug je vois :

    Unable to start activity ComponentInfo{org.example.android.apis/org.example.android.apis.MapViewDemo}: java.lang.NullPointerException

    :/

  • y0up159 le 6/01/2010

    la source du probleme c’est qd je recupére la latitude avec le location du gps

  • mohamed le 24/02/2010

    Merci pour le tuto , c’est génial et enrichissant ,
    est c que quelqu’un peut m’envoyer le code source , voici mon email : benhassine_meed@yahoo.fr
    TANKS A LOT FOR YOU ….

Exprimez-vous en laissant un commentaire !

Les commentaires ne sont pas modérés avant leur publication alors n'hésitez à vous exprimer librement.

Une seule règle : Rester un minimum poli et constructif.

Vous avez des questions d'ordre général à poser? Direction le forum.