Advertisement
Android SDK

Android SDK: Working with Google Maps - Google Places Integration

by

Google Maps provides Android developers with localization tools that can be included within your own app interfaces. In this series, we are creating a basic Android application with Google Maps and Google Places integration. In this part, we will access the Google Places API to retrieve information about places of interest near the user's current location. We will use the returned data to present markers for each place on the map in the final part of this tutorial series. We will also set the application to update the map markers when the user's location changes.

This is the third of four parts in a tutorial series on Using Google Maps and Google Places in Android apps:

App We Are Working Towards

This is a snapshot of the final app.

1. Get Google Places API Access

Step 1

In the first part of this series we used the Google API Console to get an API key for the mapping tools. Now we need to do the same for the Google Places API. Sign into your Google Account and browse to the Console again. Select Services from the options in the left-hand column and scroll down to the entry for Places API. Click to turn Places on for your account.

Places API On

Remember you need to carry out this step for any Google API service you wish to access, as your keys will not work otherwise.

Step 2

Now we need to get the key to access the Places API. Select API Access from the left-hand column in your Google APIs Console. You will see the page we used in the first part of the series to retrieve an API key for the mapping package. Although we are going to use the Places API in an Android app, the type of access is actually the same as for browser apps. When we retrieve the Places data, we request it using a URL and retrieve the results in JSON format, as you would in a Web application. Copy the key listed in the Key for browser apps section and save it.

Places API Key

We're finished with the Google APIs Console so feel free to log out of your account.


2. Build a Places Search Query

Step 1

Google Places API provides a range of information about places. For our app, we are going to use the Place Search request to return a list of places near the user's current location. We need to build the necessary elements into a URL query string to execute the Place Search. This is the basic format of the URL:

https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters

The output can be JSON or XML; we will use JSON for this app. The URL must be appended with various required parameters, including your API key, the user's location as longitude and latitude values, the radius to search within, and a boolean flag indicating whether the location was derived from a location sensor such as a GPS. There are various optional parameters you can add to your query; see the Place Search documentation for an overview. For the purpose of this app, we will add the optional types parameter to search for places of particular types.

This is an indicator of what the final query will look like:

https://maps.googleapis.com/maps/api/place/nearbysearch/json?
	location=55.864237,-4.251805999999988
	&radius=1000
	&sensor=true
	&types=food|bar|store|museum|art_gallery
	&key=your_api_key

We will build the longitude and latitude values dynamically (the hard-coded example values above are just for demonstration). The radius value is in meters, so feel free to alter it to suit the purpose of your app. You can also alter the place types if you wish; see the Supported Place Types overview for the available options. Make sure you separate the place types using the pipe "|" character. You should of course alter the key parameter value to reflect your own API key.

In your Activity class updatePlaces helper method, after the existing code in which we retrieved the user location and animated the map camera to it, create a string for the Place Search URL:

String placesSearchStr = "https://maps.googleapis.com/maps/api/place/nearbysearch/" +
	"json?location="+lat+","+lng+
	"&radius=1000&sensor=true" +
	"&types=food|bar|store|museum|art_gallery"+
	"&key=your_key_here";

Include the API key you copied from the Google APIs Console for browser apps. Note that we pass the latitude and longitude values we previously retrieved from the user's location.


3. Create an AsyncTask to Fetch Place Data in the Background

Step 1

You will need to add the following import statements to your Activity class for the code we'll use next:

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.os.AsyncTask;

Since we will fetch data over the Web, we will use an AsyncTask to carry out this process off the UI thread. This will allow us to handle the results within the UI thread, adding markers to the visible map when the app receives the Place Search data.

In your Activity class, create the AsyncTask class outline as follows:

private class GetPlaces extends AsyncTask<String, Void, String> {
//fetch and parse place data
}

The three types indicated represent the types of parameters, progress units, and the result of the background operation. We will pass the Place Search URL as a string, so this is the parameter. We won't indicate progress for this app, so the second type is void. The result of the background operation in this case will be a JSON string representing the places, so the third type is a text string.

Step 2

When you use an AsyncTask, you indicate the processing you want to occur in the background within the doInBackground method. Once that has executed and retrieved a result, the onPostExecute method will then execute. For this app, we will fetch the Place Search results in doInBackground, then parse the results in onPostExecute, creating markers to display on the map at the same time.

Inside your AsyncTask class, add the doInBackground method:

@Override
protected String doInBackground(String... placesURL) {
	//fetch places
}

The parameter type is string, as indicated by the class outline above. Let's use a String Builder to build the returned JSON text string inside this new method:

StringBuilder placesBuilder = new StringBuilder();

Although we will only pass a single Place Search query URL string, the doInBackground method expects to potentially receive an array of parameters, so add a loop to process this:

//process search parameter string(s)
for (String placeSearchURL : placesURL) {
//execute search
}

Inside the loop, create an HTTP Client object to execute the query URL:

HttpClient placesClient = new DefaultHttpClient();

As with any input/output process, we need to take care of potential errors, so add try and catch blocks next, still inside the loop:

try {
	//try to fetch the data
}
catch(Exception e){
	e.printStackTrace();
}

Inside the try block, create an HTTP Get object, passing the URL string:

HttpGet placesGet = new HttpGet(placeSearchURL);

Use the HTTP Client to execute this, retrieving an HTTP Response:

HttpResponse placesResponse = placesClient.execute(placesGet);

Let's check that we have a positive response before we attempt to carry out any further processing. Retrieve the status line:

StatusLine placeSearchStatus = placesResponse.getStatusLine();

We can only continue if we have a 200 "OK" status code indicating that the request was successful, so add a conditional statement:

if (placeSearchStatus.getStatusCode() == 200) {
//we have an OK response
}

Inside the if block, retrieve the HTTP Entity from the response object:

HttpEntity placesEntity = placesResponse.getEntity();

Now we can start to retrieve the actual content of the response, which should be the JSON string. Start by creating an Input Stream:

InputStream placesContent = placesEntity.getContent();

Now create a reader for this stream:

InputStreamReader placesInput = new InputStreamReader(placesContent);

Let's carry out our input stream processing using a Buffered Reader:

BufferedReader placesReader = new BufferedReader(placesInput);

Now we can use a loop to read the input one line at a time, appending each one to the String Builder as we go along:

String lineIn;
while ((lineIn = placesReader.readLine()) != null) {
	placesBuilder.append(lineIn);
}

This loop will continue to execute as long as there is still data to read in.

Finally, we can return the string that is handled by the String Builder. At the end of the doInBackground method after the for loop, return the JSON data:

return placesBuilder.toString();

Conclusion

The third part of this tutorial series is now complete. We set the app up to utilize Google Places API and created an inner class to fetch the place data in the background off the app's main UI thread. We executed the Place Search query and retrieved the resulting JSON. When you run your app, it will not behave any differently than it did after the previous part of this series. This is because we have not yet completed or instantiated the AsyncTask class. We will do that in the final part, after we add the onPostExecute method to parse the JSON Place Search result and translate it into map markers. We will also extend the app to update the displayed markers as the user's location changes.

Related Posts
  • Web Design
    HTML & CSS
    How to Add Branded Branch Locations to Google MapsMap thumb
    In this tutorial we'll walk through the process of creating a branded Google map for an imaginary client.Read More…
  • Code
    Android SDK
    Android SDK: App DataAndroid preview@2x
    In this series, we are learning about Android SDK development from scratch. We have already become acquainted with the structure and basic elements in an Android application, including the resources, Manifest, and user interface. As soon as you start to develop functional apps for Android, you will need to store data of one kind or another. The Android platform offers a range of options for data storage in your apps, which we will examine in this tutorial.Read More…
  • Code
    Mobile Web Apps
    Create a Location-Aware Site with Sencha Touch - Displaying LocationsLogo
    This tutorial will guide you through the development of a Location-based mobile website using the Google Place search engine and Sencha Touch 2.1 . This is the second in a two-part series, and today we'll learn how to display map markers and location details.Read More…
  • Code
    Mobile Web Apps
    Create a Location-Aware Site with Sencha TouchSenchatouch preview
    This tutorial will guide you through the development of a Location-based mobile website using the Google Place search engine and Sencha Touch 2.1 . This is a two part tutorial and in this first part we'll learn how to create a project with Sencha cmd, create a cool theme using SASS/Compass, and find services close to the user's location.Read More…
  • Code
    Android SDK
    Android SDK: Working with Google Maps - Displaying Places of InterestAndroid map preview retina
    With Google Maps in your Android apps, you can provide users with localization functions, such as geographical information. Throughout this series we have been building an Android app in which the Google Maps Android API v2 combines with the Google Places API. So far we have displayed a map, in which the user can see their current location, and we have submitted a Google Places query to return data about nearby places of interest. This required setting up API access for both services. In the final part of the series, we will parse the Google Places JSON data and use it to show the user nearby places of interest. We will also make the app update the markers when the user location changes. This is the last of four parts in a tutorial series on Using Google Maps and Google Places in Android apps: Working with Google Maps - Application Setup Working with Google Maps - Map Setup Working with Google Maps - Places Integration Working with Google Maps - Displaying Nearby Places This is a snapshot of the final app. 1. Process the Place Data Step 1 You will need to add the following import statements to your Activity class for this tutorial: [java] import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.util.Log; [/java] In the last tutorial we created an inner AsyncTask class to handle fetching the data from Google Places in the background. We added the doInBackground method to request and retrieve the data. Now we can implement the onPostExecute method to parse the JSON string returned from doInBackground, inside your AsyncTask class, after the doInBackground method: [java] protected void onPostExecute(String result) { //parse place data returned from Google Places } [/java] Step 2 Back in the second part of this series, we created a Marker object to indicate the user's last recorded location on the map. We are also going to use Markers to show the nearby places of interest. We will use an array to store these Markers. At the top of your Activity class declaration, add the following instance variable: [java] private Marker[] placeMarkers; [/java] By default, the Google Places API returns a maximum of 20 places, so let's define this as a constant too: [java] private final int MAX_PLACES = 20; [/java] When we create the Markers for each place, we will use MarkerOptions objects to configure the Marker details. Create another array instance variable for these: [java] private MarkerOptions[] places; [/java] Now let's instantiate the array. In your Activity onCreate method, after the line in which we set the map type, create an array of the maximum required size: [java] placeMarkers = new Marker[MAX_PLACES]; [/java] Now let's turn to the onPostExecute method we created. First, loop through the Marker array, removing any existing Markers. This method will execute multiple times as the user changes location: [java] if(placeMarkers!=null){ for(int pm=0; pm<placeMarkers.length; pm++){ if(placeMarkers[pm]!=null) placeMarkers[pm].remove(); } } [/java] When the app code first executes, new Markers will be created. However, when the user changes location, these methods will execute again to update the places displayed. For this reason the first thing we must do is remove any existing Markers from the map to prepare for creating a new batch. Step 3 We will be using Java JSON resources to process the retrieved place data. Since these classes throw certain exceptions, we need to build in a level of error handling throughout this section. Start by adding try and catch blocks: [java] try { //parse JSON } catch (Exception e) { e.printStackTrace(); } [/java] Inside the try block, create a new JSONObject and pass it to the result JSON string returned from doInBackground: [java] JSONObject resultObject = new JSONObject(result); [/java] If you look at the Place Search page on the Google Places API documentation, you can see a sample of what the query actually returns in JSON. You will see that the places are contained within an array named "results". Let's first retrieve that array from the returned JSON object: [java] JSONArray placesArray = resultObject.getJSONArray("results"); [/java] You should refer to the sample JSON result as we complete each section of this process - keep the page open in a browser while you complete the remainder of the tutorial. Next let's instantiate the MarkerOptions array we created with the length of the returned "results" array: [java] places = new MarkerOptions[placesArray.length()]; [/java] This should give us a MarkerOptions object for each place returned. Add a loop to iterate through the array of places: [java] //loop through places for (int p=0; p<placesArray.length(); p++) { //parse each place } [/java] Step 4 Now we can parse the data for each place returned. Inside the for loop, we will build details to pass to the MarkerOptions object for the current place. This will include latitude and longitude, place name, type and vicinity, which is an excerpt of the address data for the place. We will retrieve all of this data from the Google Places JSON, passing it to the Marker for the place via its MarkerOptions object. If any of the values are missing in the returned JSON feed, we will simply not display a Marker for that place, in case of Exceptions. To keep track of this, add a boolean flag: [java] boolean missingValue=false; [/java] Now add local variables for each aspect of the place we need to retrieve and pass to the Marker: [java] LatLng placeLL=null; String placeName=""; String vicinity=""; int currIcon = otherIcon; [/java] We create and initialize a LatLng object for the latitude and longitude, strings for the place name and vicinity and initially set the icon to use the default icon drawable we created. Now we need another try block, so that we can detect whether any values are in fact missing: [java] try{ //attempt to retrieve place data values } catch(JSONException jse){ missingValue=true; jse.printStackTrace(); } [/java] We set the missing value flag to true for checking later. Inside this try block, we can now attempt to retrieve the required values from the place data. Start by initializing the boolean flag to false, assuming that there are no missing values until we discover otherwise: [java] missingValue=false; [/java] Now get the current object from the place array: [java] JSONObject placeObject = placesArray.getJSONObject(p); [/java] If you look back at the sample Place Search data, you will see that each place section includes a "geometry" section which in turn contains a "location" section. This is where the latitude and longitude data for the place is, so retrieve it now: [java] JSONObject loc = placeObject.getJSONObject("geometry").getJSONObject("location"); [/java] Attempt to read the latitude and longitude data from this, referring to the "lat" and "lng" values in the JSON: [java] placeLL = new LatLng( Double.valueOf(loc.getString("lat")), Double.valueOf(loc.getString("lng"))); [/java] Next get the "types" array you can see in the JSON sample: [java] JSONArray types = placeObject.getJSONArray("types"); [/java] [tip] Tip: We know this is an array as it appears in the JSON feed surrounded by the "[" and "]" characters. We treat any other nested sections as JSON objects rather than arrays. [/tip] Loop through the type array: [java] for(int t=0; t<types.length(); t++){ //what type is it } [/java] Get the type string: [java] String thisType=types.get(t).toString(); [/java] We are going to use particular icons for certain place types (food, bar and store) so add a conditional: [java] if(thisType.contains("food")){ currIcon = foodIcon; break; } else if(thisType.contains("bar")){ currIcon = drinkIcon; break; } else if(thisType.contains("store")){ currIcon = shopIcon; break; } [/java] The type list for a place may actually contain more than one of these places, but for convenience we will simply use the first one encountered. If the list of types for a place does not contain any of these, we will leave it displaying the default icon. Remember that we specified these types in the Place Search URL query string last time: [text] food|bar|store|museum|art_gallery [/text] This means that the only place types using the default icon will be museums or art galleries, as these are the only other types we asked for. After the loop through the type array, retrieve the vicinity data: [java] vicinity = placeObject.getString("vicinity"); [/java] Finally, retrieve the place name: [java] placeName = placeObject.getString("name"); [/java] Step 5 After the catch block in which you set the missingValue flag to true, check that value and set the place MarkerOptions object to null, so that we don't attempt to instantiate any Marker objects with missing data: [java] if(missingValue) places[p]=null; [/java] Otherwise, we can create a MarkerOptions object at this position in the array: [java] else places[p]=new MarkerOptions() .position(placeLL) .title(placeName) .icon(BitmapDescriptorFactory.fromResource(currIcon)) .snippet(vicinity); [/java] Step 6 Now, at the end of onPostExecute after the outer try and catch blocks, loop through the array of MarkerOptions, instantiating a Marker for each, adding it to the map and storing a reference to it in the array we created: [java] if(places!=null && placeMarkers!=null){ for(int p=0; p<places.length && p<placeMarkers.length; p++){ //will be null if a value was missing if(places[p]!=null) placeMarkers[p]=theMap.addMarker(places[p]); } } [/java] Storing a reference to the Marker allows us to easily remove it when the places are updated, as we implemented at the beginning of the onPostExecute method. Notice that we include two conditional tests each time this loop iterates, in case the Place Search did not return the full 20 places. We also check in case the MarkerOptions is null, indicating that a value was missing. Step 7 Finally, we can instantiate and execute our AsyncTask class. In your updatePlaces method, after the existing code in which we built the search query string, start this background processing to fetch the place data using that string: [java] new GetPlaces().execute(placesSearchStr); [/java] You can run your app now to see it in action. It should display your last recorded location together with nearby places of interest. The colors you see on the Markers will depend on the places returned. Here is the app displaying a user location in Glasgow city center, UK: Perhaps unsurprisingly a lot of the places listed in Glasgow are bars. When the user taps a Marker, they will see the place name and snippet info: 2. Update With User Location Changes Step 1 The app as it stands will execute once when it is launched. Let's build in the functionality required to make it update to reflect changes in the user location, refreshing the nearby place Markers at the same time. Alter the opening line of the Activity class declaration to make it implement the LocationListener interface so that we can detect changes in the user location: [java] public class MyMapActivity extends Activity implements LocationListener { [/java] A Location Listener can respond to various changes, each of which uses a dedicated method. Inside the Activity class, implement these methods: [java] @Override public void onLocationChanged(Location location) { Log.v("MyMapActivity", "location changed"); updatePlaces(); } @Override public void onProviderDisabled(String provider){ Log.v("MyMapActivity", "provider disabled"); } @Override public void onProviderEnabled(String provider) { Log.v("MyMapActivity", "provider enabled"); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { Log.v("MyMapActivity", "status changed"); } [/java] The only one we are really interested in is the first, which indicates that the location has changed. In this case we call the updatePlaces method again. Otherwise we simply write out a Log message. At the end of the updatePlaces method, add a request for the app to receive location updates: [java] locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this); [/java] We use the Location Manager we created earlier in the series, requesting updates using the network provider, at delays of 30 seconds (indicated in milliseconds), with a minimum location change of 100 meters and the Activity class itself to receive the updates. You can, of course, alter some of the parameters to suit your own needs. [tip] Tip: Although the requestLocationUpdates method specifies a minimum time and distance for updates, in reality it can cause the onLocationChanged method to execute much more often, which has serious performance implications. In any apps you plan on releasing to users, you should therefore limit the frequency at which your code responds to these location updates. The alternative requestSingleUpdate method used on a timed basis may be worth considering. [/tip] Step 2 Last but not least, we need to take care of what happens when the app pauses and resumes. Override the two methods as follows: [java] @Override protected void onResume() { super.onResume(); if(theMap!=null){ locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this); } } @Override protected void onPause() { super.onPause(); if(theMap!=null){ locMan.removeUpdates(this); } } [/java] We check for the GoogleMap object before attempting any processing, as in onCreate. If the app is pausing, we stop it from requesting location updates. If the app is resuming, we start requesting the updates again. [tip] Tip: We've used the LocationManager.NETWORK_PROVIDER a few times in this series. If you are exploring localization functionality in your apps, check out the alternative getBestProvider method with which you can specify criteria for Android to choose a provider based on such factors as accuracy and speed. [/tip] Before We Finish That pretty much completes the app! However, there are many aspects of the Google Maps Android API v2 that we have not even touched on. Once you have your app running you can experiment with features such as rotation and tilting. The updated maps service displays indoor and 3D maps in certain places. The following image shows the 3D facility with the app if the user location was in Venice, Italy: This has the map type set to normal - here is another view of Venice with the hybrid map type set: Conclusion In this tutorial series we have worked through the process of integrating both Google Maps and Google Places APIs in a single Android app. We handled API key access, setting up the development environment, workspace and application to use Google Play Services. We utilized location data, showing the user location together with nearby places of interest, and displaying the data with custom UI elements. Although what we have covered in this series is fairly extensive, it really is only the beginning when it comes to building localization features into Android apps. With the release of Version 2 of the Maps API, Android apps are set to take such functions to the next level.Read More…
  • Code
    Android SDK
    Build a Twitter Search App: Fetch & Parse the Twitter JSON FeedTwitter bird
    This two-part tutorial series will introduce you to the fundamentals of working with RESTful web services using the Android SDK. Along the way, you’ll learn how to perform searches against the public Twitter API!Read More…