Fork me on GitHub

Maps V2 Android

Guide for the new Android Maps V2 API. Includes a sample app, full source code, screencast and guides.
View on GitHub

Using Google APIs on your map : Directions and Places

In the following part I’ll show you howto integrate with 2 popular Google APIs that you can use to enrich your Maps experience, the Directions API and the Places API.

The goal is to create a simple Directions application where the user can enter an origin and a destination. As the user is typing the origin / destination, he’ll receive hints from the Google Places API (autocompletion). After the user has entered an origin and a destination, we’ll show the directions on a map by drawing a polyline and animating along the path.

We’re going to be using AsyncTask to perform the HTTP processing in the background off the main thread. For the actual HTTP communication we’re going to be using the Google HTTP Client Library for Java.

The Google Directions API

The Google Directions API is a service that calculates directions between locations using an HTTP request. We’ll use it to draw a path (a polyline) between 2 points (origin and destination).

The Google Places API

The Google Places API is a service that returns information about Places — defined within this API as establishments, geographic locations, or prominent points of interest — using HTTP requests. Place requests specify locations as latitude/longitude coordinates. We’re going to be using a small part of the Google Places API, namely the Places Autocomplete API.

Places Autocomplete

The Google Places Autocomplete API is a web service that returns Place information based on text search terms, and, optionally, geographic bounds. The API can be used to provide autocomplete functionality for text-based geographic searches, by returning Places such as businesses, addresses, and points of interest as a user types.

Directions input

We’re going to use a GridLayout to build our directions input layout. We add a couple of labels and 2 AutoCompleteTextView components to the layout. The AutoCompleteTextView components will be used to pull in data from the Google Places API as the user is typing.

<android.support.v7.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:alignmentMode="alignBounds"
	android:orientation="horizontal"
	android:padding="16dp"
	android:useDefaultMargins="true"
	app:columnCount="1" >

	<TextView
		android:id="@+id/from_label"
		app:layout_gravity="fill_horizontal"
		android:text="@string/from"
		android:textColor="?android:textColorSecondary" />

	<AutoCompleteTextView
		android:id="@+id/from"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		app:layout_gravity="fill_horizontal"
		android:completionThreshold="3" />

	<TextView
		android:id="@+id/to_label"
		app:layout_gravity="fill_horizontal"
		android:text="@string/to"
		android:textColor="?android:textColorSecondary" />

	<AutoCompleteTextView
		android:id="@+id/to"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		app:layout_gravity="fill_horizontal"
		android:completionThreshold="3" />

	<Button
		android:id="@+id/load_directions"
		android:layout_columnSpan="@integer/create_account_pane_column_count"
		app:layout_gravity="fill_horizontal"
		android:text="@string/load_directions" />

</android.support.v7.widget.GridLayout>

In our Activity, we hook up our origin and destination AutoCompleteTextView components, set some default values, and attach a PlacesAutoCompleteAdapter The PlacesAutoCompleteAdapter is responsible for delivering the places associated with the text that the user has keyed in.

from = (AutoCompleteTextView) findViewById(R.id.from);
to = (AutoCompleteTextView) findViewById(R.id.to);

from.setText("Fisherman's Wharf, San Francisco, CA, United States");
to.setText("The Moscone Center, Howard Street, San Francisco, CA, United States");

from.setAdapter(new PlacesAutoCompleteAdapter(this, android.R.layout.simple_dropdown_item_1line));
to.setAdapter(new PlacesAutoCompleteAdapter(this, android.R.layout.simple_dropdown_item_1line));

The PlacesAutoCompleteAdapter is an exact copy (rip-off) of the one that can be found in the excellent Adding Autocomplete to your Android App article.

It looks like this :

private class PlacesAutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {
	private ArrayList<String> resultList;
	
	public PlacesAutoCompleteAdapter(Context context, int textViewResourceId) {
		super(context, textViewResourceId);
	}
	
	@Override
	public int getCount() {
		return resultList.size();
	}

	@Override
	public String getItem(int index) {
		return resultList.get(index);
	}

	@Override
	public Filter getFilter() {
		Filter filter = new Filter() {
			@Override
			protected FilterResults performFiltering(CharSequence constraint) {
				FilterResults filterResults = new FilterResults();
				if (constraint != null) {
					// Retrieve the autocomplete results.
					resultList = autocomplete(constraint.toString());
					
					// Assign the data to the FilterResults
					filterResults.values = resultList;
					filterResults.count = resultList.size();
				}
				return filterResults;
			}

			@Override
			protected void publishResults(CharSequence constraint, FilterResults results) {
				if (results != null && results.count > 0) {
					notifyDataSetChanged();
				}
				else {
					notifyDataSetInvalidated();
				}
			}};
		return filter;
	}
}

The actual activity looks like this:

directions_input

As the user is typing, he’ll get the hints from the Google Places Autocompletion / AutoCompleteTextView:

google-places-autocomplete

The grunt of the work happens in the autocomplete method. I’ve used a different approach for that one as I wanted to use the Google HTTP Client Library for Java to connect to the Google API and parse the results, as opposed to using a lower-level HttpURLConnection to make the HTTP connection, and parsing the JSON response using a JSONObject.

Communicating the the Google Services

The Google HTTP Client Library for Java provides an easy and convenient way to interact with JSON based webservices like the ones we’ll be discussing today

We start our code by creating an HttpTransport and a JsonFactory. The type of HttpTransport that is to be used depends on the version of the target Android environment.

Application targeted at Gingerbread or higher should use NetHttpTransport. NetHttpTransport is built into the Android SDK and is found in all Java SDKs. In earlier version of Android SDKs the implementation of HttpURLConnection/NetHttpTransport was buggy, and using the ApacheHttpTransport was preferred. If you are targeting multiple Android API levels, simply call AndroidHttp.newCompatibleTransport() and it will decide of these two to use based on the Android SDK level.

The JSON Factory is the low-level JSON library implementation based on Jackson 2, needed to parse the JSON responses.

	
static final HttpTransport HTTP_TRANSPORT = AndroidHttp.newCompatibleTransport();
static final JsonFactory JSON_FACTORY = new JacksonFactory();

We’ll use the Google HTTP Client Library for Java for both the Places Autocompletion API call as well as for the Directions API call.

Places Autocompletion API

Using our HttpTransport, we create an HttpRequestFactory (responsible for setting the parser) that we’ll use to build our actual HTTP requests.

HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
		 @Override
		 public void initialize(HttpRequest request) {
			 request.setParser(new JsonObjectParser(JSON_FACTORY));
		 }
	 }
);

But before we can make a request, we need to build a URL. this is done using the GenericUrl object. We pass in an encoded URL and append whatever parameters we need. In this case, we provide an input, API key and sensor value.

GenericUrl url = new GenericUrl(PLACES_API_AUTOCOMPLETION);
url.put("input", input);
url.put("key", PLACES_API_KEY);
url.put("sensor",false);

As this is a simple REST call, you can enter the following URL in your browser to see what it returns

https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Fisher&key=[YOUR API KEY]&sensor=false

Now that we have create a URL, we can go ahead and build a GET request with it.

HttpRequest request = requestFactory.buildGetRequest(url);	

This will give us an HttpRequest that we can execute by calling its execute() method. This will give us the HttpResponse.

HttpResponse httpResponse = request.execute();

The cool thing about the Google HTTP Client Library for Java is that it can parse the response properly and take care of the JSON response handling for us. This releaves us from the burden of writing our own JSON handling.

When we call the parseAs() method on the HttpResponse object, we can provide a java class that the library will use to convert the JSON response. The parseAs() method will return an object of that type that we can use the parse the response in a very straightforward way without having to worry about using JSON directly.

PlacesResult directionsResult = httpResponse.parseAs(PlacesResult.class);

List<Prediction> predictions = directionsResult.predictions;
for (Prediction prediction : predictions) {
	resultList.add(prediction.description);
}

So how will the library to the mapping from JSON to our Java object ? Well, we can provide hints in our PlacesResult class (and child classes) using the @Key annotation.

Remember the Autocompletion API returned a JSON string like this (abbreviated) :

{
   "predictions" : [
	  {
		 "description" : "Fishers, IN, United States",
		 "id" : "51f9b9731d3996a2e3919fbc0b8d7c52a215b918",
		 "matched_substrings" : [...],
		 "reference" : "CkQy...",
		 "terms" : [...],
		 "types" : [ "locality", "political", "geocode" ]
	  },
	  {
		 "description" : "Fishersville, VA, United States",
		 "id" : "25c391688a6f5479f94f53231beac994f79e80bc",
		 "matched_substrings" : [...],
		 "reference" : "CkQ3A...",
		 "terms" : [...],
		 "types" : [ "locality", "political", "geocode" ]
	  },
	  {
		 "description" : "Fisherman's Wharf, San Francisco, CA, United States",
		 "id" : "e390dabe9db9ed93c37572ced9a94400f88f1ad4",
		 "matched_substrings" : [...],
		 "reference" : "ClRL...",
		 "terms" : [...],
		 "types" : [ "neighborhood", "political", "geocode" ]
	  },
	  .....
   ],
   "status" : "OK"
}

In order to model this using java we create the following value objects:

public static class PlacesResult {

	@Key("predictions")
	public List<Prediction> predictions;

  }

public static class Prediction {
  @Key("description")
  public String description;
  
  @Key("id")
  public String id;
  
}	  

This allows you to parse the response very easily

List<Prediction> predictions = directionsResult.predictions;
for (Prediction prediction : predictions) {
	resultList.add(prediction.description);
}

The complete code is pretty condensed and simple:

private ArrayList<String> autocomplete(String input) {
	ArrayList<String> resultList = new ArrayList<String>();
	
	try {
	
		HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
				 @Override
				 public void initialize(HttpRequest request) {
					 request.setParser(new JsonObjectParser(JSON_FACTORY));
				 }
			 }
		);
		
		GenericUrl url = new GenericUrl(PLACES_API_BASE + TYPE_AUTOCOMPLETE + OUT_JSON);
		url.put("input", input);
		url.put("key", PLACES_API_KEY);
		url.put("sensor",false);
 
		HttpRequest request = requestFactory.buildGetRequest(url);
		HttpResponse httpResponse = request.execute();
		PlacesResult directionsResult = httpResponse.parseAs(PlacesResult.class);

		List<Prediction> predictions = directionsResult.predictions;
		for (Prediction prediction : predictions) {
			resultList.add(prediction.description);
		}
	} catch (Exception ex) {
		ex.printStackTrace();
	}
	return resultList;
}	

Directions API call

The Directions API call use an AsyncTask to fetch the data in the background.

private class DirectionsFetcher extends AsyncTask<URL, Integer, String> {

We define a list of LatLng objects that we’ll use to store our results

private List<LatLng> latLngs = new ArrayList<LatLng>();

And we’ll do the heavy lifting in the doInBackground method.

As this is a simple REST call, you can enter the following URL in your browser to see what it returns

http://maps.googleapis.com/maps/api/directions/json?origin=Chicago,IL&destination=Los%20Angeles,CA&sensor=false

Again, the GenericUrl object takes care of adding and properly encoding the query parameters.

	
 GenericUrl url = new GenericUrl("http://maps.googleapis.com/maps/api/directions/json");
 url.put("origin", origin);
 url.put("destination", destination);
 url.put("sensor",false);

We’ve again built our own java object model based on the Directions API REST response.

DirectionsResult directionsResult = httpResponse.parseAs(DirectionsResult.class);

The Directions API returned a JSON string like this (removed the legs info for abbrevity.)

{
   "routes" : [
	  {
		 "bounds" : {
			"northeast" : {
			   "lat" : 41.9054370,
			   "lng" : -87.62978720
			},
			"southwest" : {
			   "lat" : 34.05236330,
			   "lng" : -118.24356010
			}
		 },
		 "copyrights" : "Map data ©2013 Google",
		 "legs" : [
			.. legs info
		 ],
		 "overview_polyline" : {
			"points" : "eir~Fd..."
		 },
		 "summary" : "I-80 W",
		 "warnings" : [],
		 "waypoint_order" : []
	  }
   ],
   "status" : "OK"	
}

The Directions API always returns a single route if no alternatives are requested, so we know we need to search in the first route that is returned. Within that route, we are interested in the overview_polyline field that in turn contains a points field.

This points field contains an object holding an array of encoded points that represent an approximate (smoothed) path of the resulting directions.

String encodedPoints = directionsResult.routes.get(0).overviewPolyLine.points;

The following value object structure can be used to traverse the JSON path above. This is again implemented using a simple value object model.

public static class DirectionsResult {
	@Key("routes")
	public List<Route> routes;
}

public static class Route {
	@Key("overview_polyline")
	public OverviewPolyLine overviewPolyLine;
}

public static class OverviewPolyLine {
	@Key("points")
	public String points;
}

You can see how it maps to the following JSON structure

{ "routes" : [
	  {
		 "overview_polyline" : {
			"points" : "eir~Fdezu..."
		 }
	  }
   ]
}

The full code can be found here.

	
protected String doInBackground(URL... urls) {
	try {
		HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
		@Override
		public void initialize(HttpRequest request) {
		request.setParser(new JsonObjectParser(JSON_FACTORY));
		}
		});

		GenericUrl url = new GenericUrl("http://maps.googleapis.com/maps/api/directions/json");
		url.put("origin", "Chicago,IL");
		url.put("destination", "Los Angeles,CA");
		url.put("sensor",false);

		HttpRequest request = requestFactory.buildGetRequest(url);
		HttpResponse httpResponse = request.execute();
		DirectionsResult directionsResult = httpResponse.parseAs(DirectionsResult.class);
		String encodedPoints = directionsResult.routes.get(0).overviewPolyLine.points;
		latLngs = PolyUtil.decode(encodedPoints);
	} catch (Exception ex) {
		ex.printStackTrace();
	}
	return null;
	}

	protected void onProgressUpdate(Integer... progress) {
	}

	protected void onPostExecute(String result) {
		clearMarkers();
		addMarkersToMap(latLngs);
	}
}	

References