8.1: Using the Places API
Contents:
- Introduction
- What you should already KNOW
- What you will LEARN
- What you will DO
- App overview
- Task 1. Sign up and obtain API keys
- Task 2. Get details on the current place
- Task 3. Add the place-picker UI
- Coding challenge
- Solution code
- Summary
- Related concept
- Learn more
The Location APIs can provide timely and accurate location information, but these APIs only return a set of geographic coordinates. In 7.1 Using the device location, you learned how to make geographic coordinates more useful by reverse geocoding them into physical addresses.
But what if you want to know more about a location, like the type of place it is? In this practical, you use the Google Places API for Android to obtain details about the device's current location. You also learn about the place picker and the place-autocomplete APIs. The place picker and autocomplete let users search for places rather than having your app detect the device's current place.
What you should already KNOW
You should be familiar with:
- Creating, building, and running apps in Android Studio.
- The activity lifecycle.
- Including external libraries in your
build.gradle
file. - Creating responsive layouts with
ConstraintLayout
.
What you will LEARN
You will learn how to:
- Get details about the user's current place.
- Launch the place-picker UI so the user can select a place.
- Include a place-autocomplete fragment to allow the user to search for places.
What you will DO
- Get an API key from the Google API Console and register the key to your app.
- Get the name of the place where the device is located.
- If the place is a school, gym, restaurant, or library, change an image in the UI to reflect the place type.
- Add a button to allow the user to select a place.
- Add a search bar that autocompletes a user's search for a place.
App overview
The app for this practical extends the WalkMyAndroid app from the previous practical in two ways:
Get details about the current location of the device, including the place type and place name. Display the name in the label
TextView
and change the Android robot image to reflect the place type.Add a Pick a Place button that launches the place-picker UI, allowing the user to select a place.
Task 1. Sign up and obtain API keys
In this task you set up the starter app, WalkMyAndroidPlaces-Starter, with an API key. Every app that uses the Google Places API for Android must have an API key. The key is linked to the app by the app's package name and by a digital certificate that's unique to the app.
To set up an API key in your app, you need to do the following:
- Get information about your app's digital certificate.
- Get an API key. To do this, you register a project in the Google API Console and add the Google Places API for Android as a service for the project.
- Add the key to your app by adding a
meta-data
element to yourAndroidManifest.xml
file.
1.1 Get your app's certificate information
The API key is based on a short form of your app's digital certificate, known as the certificate's SHA-1 fingerprint . This fingerprint uniquely identifies the app, and identifies you as the app's owner, for the lifetime of the app.
You might have two certificates:
- A debug certificate , which is the certificate you use for this practical. The Android SDK tools generate a debug certificate when you do a debug build. Don't attempt to publish an app that's signed with a debug certificate. The debug certificate is described in more detail in Sign your debug build.
- A release certificate , which you don't need for this practical. The Android SDK tools generate a release certificate when you do a release build. You can also generate a release certificate using the
keytool
utility. Use the release certificate when you're ready to release your app to the world. For information about release certificates, see Signup and API Keys.
For this practical, make sure that you use the debug certificate.
To view the debug certificate's fingerprint:
Locate your debug
keystore
file, which is nameddebug.keystore
. By default, the file is stored in the same directory as your Android Virtual Device (AVD) files:- macOS and Linux:
~/.android/
- Windows Vista and Windows 7:
C:\Users\your_user_name.android\
The file is created the first time you build your project.
- macOS and Linux:
List the SHA-1 fingerprint:
- For Linux or macOS, open a terminal window and enter the following:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
- For Windows Vista and Windows 7, run:
You should see output similar to the following:keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore"\ -alias androiddebugkey -storepass android -keypass android
The line that begins withAlias name: androiddebugkey Creation date: Jan 01, 2013 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=Android Debug, O=Android, C=US Issuer: CN=Android Debug, O=Android, C=US Serial number: 4aa9b300 Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033 Certificate fingerprints: MD5: AE:9F:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:F9 SHA1: BB:0D:AC:44:D3:21:E1:41:07:71:9C:62:90:AF:A4:66:6E:44:5D:95 Signature algorithm name: SHA1withRSA Version: 3
SHA1
contains the certificate's SHA-1 fingerprint. The fingerprint is the sequence of 20 two-digit hexadecimal numbers separated by colons.
- For Linux or macOS, open a terminal window and enter the following:
Copy this value to your clipboard. You need it in the next set of steps.
- Download the starter code for this practical, WalkMyAndroidPlaces-Starter.
- Open Android Studio and open your
AndroidManifest.xml
file. Note thepackage
string in themanifest
tag. You need the package name in future steps.
keystore
file and key, don't enter the storepass
or keypass
arguments on the command line unless you're confident of your computer's security. For example, on a public computer, someone could look at your terminal window history or list of running processes, get the password, and have write-access to your signing certificate. That person could modify your app or replace your app with their own.
1.2 Get an API key from the Google API Console
- Go to the Google API Console.
- Create or select a project. You can reuse the same project and API key for multiple apps.
- From the Dashboard page, click ENABLED APIS AND SERVICES. The API Library opens.
- Search for "Places" and select Google Places API for Android.
- Click ENABLE.
- If a warning appears telling you to create credentials, click Create credentials. Otherwise, open the Credentials page and click Create credentials.
- If you see a Find out what kind of credentials you need step, skip the prompts. Click directly on the API key link.
- Name the key whatever you like.
Restrict the key to Android apps:
- Follow the prompts to restrict the key to Android apps.
- Click Add package name and fingerprint.
Add your app's SHA-1 fingerprint to the key. (You copied this fingerprint to your clipboard in 1.1 Get your app's certificate information.) Also enter the package name from Android Studio.
For example:
BB:0D:AC:74:D3:21:E1:43:67:71:9B:62:91:AF:A1:66:6E:44:5D:75 com.example.android.walkmyandroidplaces
Save your changes.
On the Credentials page, your new Android-restricted API key appears in the list of API keys for your project. An API key is a string of characters, something like this:
AIzaSyBdVl-cTCSwYZrZ95SuvNw0dbMuDt1KG0
- Copy the API key to your clipboard. You'll paste the key into your app manifest in the next step.
1.3 Add the key to the manifest
Add your API key to your AndroidManifest.xml
file as shown in the following code. Replace YOUR_API_KEY
with your own API key:
<application>
...
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY"/>
...
Task 2. Get details on the current place
Now that you have your API key set up, you're ready to start using the Places API. In this task, you use the PlaceDetectionClient.getCurrentPlace()
method to obtain the place name and place type for the device's current location. You use these place details to enhance the WalkMyAndroidPlaces UI.
2.1 Add the Places API to your project
To use the Places API, you need to add it to the app-level build.gradle
file. You also need to connect to the API using the GoogleApiClient
class.
- Add the following statement to your app-level
build.gradle
file. ReplaceXX.X.X
with the appropriate support library version. For the latest version number, see Add Google Play Services to Your Project.compile 'com.google.android.gms:play-services-places:XX.X.X'
- In the
MainActivity
, in theonCreate()
method, initialize aPlaceDetectionClient
object. You use this object to get information about the device's current location.mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);
Note: To use thePlaceDetectionClient
interface, your app needs theACCESS_FINE_LOCATION
permission. You don't need to add this permission, because the WalkMyAndroidPlaces-Starter code includes it.
2.2 Get the place name
In this step, you extend the WalkMyAndroidPlaces app to show the place name associated with the current device location.
- In the
MainActivity
, create aString
member variable calledmLastPlaceName
. This member variable will hold the name of the device's most probable location. - In
strings.xml
, modify theaddress_text
string to include the place name as an additional variable:<string name="address_text">"Name: %1$s \n Address: %2$s \n Timestamp: %3$tr"</string>
- In the
onClick()
method for the Start Tracking Location button, add another argument to thesetText()
call. Pass in theloading
string so that theTextView
label shows "Loading..." for theName
andAddress
lines:
When themLocationTextView.setText(getString(R.string.address_text, getString(R.string.loading),// Name getString(R.string.loading),// Address new Date())); // Timestamp
AsyncTask
returns anAddress
, theonTaskCompleted()
method is called. In theonTaskComplete()
method, obtain the current place name and update theTextView
. To get the current place name, callPlaceDetectionClient.getCurrentPlace()
.
The getCurrentPlace()
method returns a Task
object. A Task
object represents an asynchronous operation and contains a parameterized type that's returned when the operation completes in its onComplete()
callback. In this case, the parameterized type is a PlaceLikelihoodBufferResponse
.
The returned PlaceLikelihoodBufferResponse
instance is a list of Place
objects, each with a "likelihood" value between 0 and 1. The likelihood value indicates the likelihood that the device is at that place. The strategy shown below is to use a loop to determine the place that has the highest likelihood, then display that place name.
- In the
onTaskCompleted()
method, callgetCurrentPlace()
on yourPlaceDetectionClientApi
instance. Store the result in a local variable. Because you are interested in all places, you can pass innull
for thePlaceFilter
:Task<PlaceLikelihoodBufferResponse> placeResult = mPlaceDetectionClient.getCurrentPlace(null);
- The
getCurrentPlace()
method call is underlined in Android Studio, because the method may throw aSecurityException
if you don't have the right location permissions. Have theonTaskCompleted()
method throw aSecurityException
to remove the warning. - Add an
OnCompleteListener
to theplaceResult
:placeResult.addOnCompleteListener (new OnCompleteListener<PlaceLikelihoodBufferResponse>() { @Override public void onComplete(@NonNull Task<PlaceLikelihoodBufferResponse> task) { });
- Create an if/else statement to check whether the
Task
was successful:if (task.isSuccessful()) { } else { {
- If the
Task
was successful, callgetResult()
on the task to obtain thePlaceLikelihoodBufferResponse
. Initialize an integer to hold the maximum value. Initialize aPlace
to hold the highest likelihoodPlace
object.if (task.isSuccessful()) { PlaceLikelihoodBufferResponse likelyPlaces = task.getResult(); float maxLikelihood = 0; Place currentPlace = null; }
- Iterate over each
PlaceLikelihood
object and check whether it has the highest likelihood so far. If it does, update themaxLikelihood
andcurrentPlace
objects:if (task.isSuccessful()) { PlaceLikelihoodBufferResponse likelyPlaces = task.getResult(); float maxLikelihood = 0; Place currentPlace = null; for (PlaceLikelihood placeLikelihood : likelyPlaces) { if (maxLikelihood < placeLikelihood.getLikelihood()) { maxLikelihood = placeLikelihood.getLikelihood(); currentPlace = placeLikelihood.getPlace(); } } }
- If the
currentPlace
is notnull
, update theTextView
with the result. The following code should all be within theif (task.isSuccessful())
loop:if (currentPlace != null) { mLocationTextView.setText( getString(R.string.address_text, currentPlace.getName(), result //This is the address from the AsyncTask, System.currentTimeMillis())); }
- After you use the place information, release the buffer:
likelyPlaces.release();
- In the
else
block for the case where theTask
is not successful, show an error message instead of a place name:else { mLocationTextView.setText( getString(R.string.address_text, "No Place name found!", result, System.currentTimeMillis())); }
- Run the app. You see the place name along with the address in the label
TextView
.
2.3 Get the place type
The Place
object you obtained from the PlaceLikelihood
object contains a lot more than just the name of the place. The object can also include the place type, rating, price level, website URL, and more. (For a list of all the fields, see the Place
reference.)
In this step, you change the image of the Android robot to reflect the place type of the current location. The starter code includes a bitmap image for a "plain" Android robot, plus images for four other Android robots, one for each these place types: school, gym, restaurant, and library.
If you want to add support for other place types, use the Androidify tool to create a custom Android robot image and include the image in your app.
In
MainActivity
, create asetAndroidType()
method that uses aPlace
object. Have the method assign the appropriate drawable to theImageView
, based on the place type:private void setAndroidType(Place currentPlace) { int drawableID = -1; for (Integer placeType : currentPlace.getPlaceTypes()) { switch (placeType) { case Place.TYPE_SCHOOL: drawableID = R.drawable.android_school; break; case Place.TYPE_GYM: drawableID = R.drawable.android_gym; break; case Place.TYPE_RESTAURANT: drawableID = R.drawable.android_restaurant; break; case Place.TYPE_LIBRARY: drawableID = R.drawable.android_library; break; } } if (drawableID < 0) { drawableID = R.drawable.android_plain; } mAndroidImageView.setImageResource(drawableID); }
Note: ThesetAndroidType()
method is where you can add support for more place types. All you need to do is add anothercase
to theswitch
statement and select the appropriate drawable. For a list of supported place types, see Place Types.In the
onComplete()
callback where you obtain the currentPlace
object, callsetAndroidType()
. Pass in thecurrentPlace
object.- Run your app. Unless you happen to be in one of the supported place types, you don't see any difference in the Android robot image. To get around this, run the app on an emulator and follow the steps below to set up fake locations.
How to test location-based features on an emulator
Testing location-based features on an emulator can be challenging. The FusedLocationProviderClient
and the Places API have to use a location that you provide through the emulator settings.
To simulate a location, use a GPX file, which provides a set of GPS coordinates over time:
- Download the WalkMyAndroidPlaces-gpx file. The file contains five locations. The first location doesn't correspond to any of the supported place types. The other four locations have the place types that the WalkMyAndroidPlaces app supports: school, gym, restaurant, library.
- Start an emulator of your choice.
- To navigate to your emulator settings, select the three dots at the bottom of the menu next to the emulator, then select the Location tab.
In the bottom right corner, click Load GPX/KML. Select the file you downloaded.
Five locations load in the GPS data playback window.
Notice the Delay column. By default, the emulator changes the location every 2 seconds. Change the delay to 10 seconds for each item except the first item, which should load immediately and have a delay of 0.
(A delay of 10 seconds makes sense because your location updates happen approximately every 10 seconds. Recall the
LocationRequest
object, in which the interval is set to 10,000 milliseconds, or 10 seconds.)Run the WalkMyAndroid app on the emulator to start tracking the device location.
- Use the play button in the bottom left corner of the emulator Location tab to deliver the GPX file's location information to your app. The location
TextView
and the Android robot image should update every 10 seconds to reflect the new locations as they are "played" by the GPX file!
The screenshot below shows the Location tab (1) for emulator location settings, the GPS data playback button (2), and the Load GPX/KML button (3).
Task 3. Add the place-picker UI
At this point, the WalkMyAndroidPlaces app only looks for places in the device's current location, but there are many use cases where you want the user to select a location from a map. For example, if you create an app to help users decide on a restaurant, you want to show restaurants in the area that the user selects, not just restaurants in the user's current location.
Having the user select a location from a map seems complicated: you need a map with places of interest already on it, and you need a way for the user to search for a place and select a place. Fortunately, the Place API includes the place picker, a UI that greatly simplifies the work.
In this task, you add a button that launches the place-picker UI. The place-picker UI lets the user select a place, and it displays the place information in the UI, as before. (It displays the relevant Android robot image, if available, and it displays the place name, address, and update time.)
3.1 Add a PlacePicker button
The place-picker UI is a dialog where the user can search for a place and select a place. To add the place picker to your app, use the PlacePicker.IntentBuilder
intent to launch a special Activity
. Get the result in your activity's onActivityResult()
method:
- Add a button next to the Start Tracking Location button. Use "Pick a Place" as the button's text in both the portrait and the landscape layout files.
Add a click handler that executes the following code to start the
PlacePicker
. Create an arbitrary constant calledREQUEST_PICK_PLACE
that you'll use later to obtain the result. (This constant should be different from your permission-check integer.)private static final int REQUEST_PICK_PLACE = 2; PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder(); try { startActivityForResult(builder.build(MainActivity.this), REQUEST_PICK_PLACE); } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) { e.printStackTrace(); }
- Run your app. When you click Pick a Place, a
PlacePicker
dialog opens. In the dialog, you can search for and select any place.
The next step is to get whatever place the user selects in onActivityResult()
, then update the TextView
and Android robot image.
3.2 Obtain the selected place
The result from the PlacePicker
is sent automatically to the activity's onActivityResult()
override method. Use the passed-in requestCode
integer to get the result. The result should match the integer you passed into the startActivityForResult()
method.
- To override the
onActivityResult()
method, select Code > Override Methods in Android Studio. Find and select theonActivityResult()
method, then click OK. In
onActivityResult()
, create anif
statement that checks whether therequestCode
matches your request integer.To get the
Place
object that was selected from thePlacePicker
, use thePlacePicker.getPlace()
method.If the
resultCode
isRESULT_OK
, get the selected place from the dataIntent
using thePlacePicker.getPlace()
method. Pass in the application context and the dataIntent
, which the system passes intoonActivityResult()
.If the
resultCode
isn'tRESULT_OK
, show a message that says that a place was not selected.if (resultCode == RESULT_OK) { Place place = PlacePicker.getPlace(this, data); } else { mLocationTextView.setText(R.string.no_place); }
- After you get the
Place
object, callsetAndroidType()
. Pass in your obtained object. - Update the label
TextView
with the place name and address from thePlace
object. Set the update time to be the current time:mLocationTextView.setText( getString(R.string.address_text, place.getName(), place.getAddress(), System.currentTimeMillis()));
- Run the app. You can now use the
PlacePicker
to choose any place in the world, provided that the place exists in the Places API. To test your app's functionality, search for one of the place types for which you have an Android robot image.
PlacePicker
, this data is not persisted using the SavedInstanceState
. For this reason, when you rotate the device, the app resets to the initial state.
Coding challenge
Challenge: Add a place autocomplete search dialog UI element to your Activity
. The place autocomplete dialog lets the user search for a place without launching the PlacePicker
.
Solution code
Summary
- To use the Google Places API for Android, you must create an API key that's restricted to Android apps. You do this in the Google API Console. In your project in the API Console, you also need to enable the Google Places API for Android.
- Include the API key in a metadata tag in your
AndroidManifest.xml
file. - The
Place
object contains information about a specific geographic location, including the place name, address, coordinates, and more. - Use the
PlaceDetectionClient.getCurrentPlace()
method to get information about the device's current location. PlaceDetectionClient.getCurrentPlace()
returns aPlaceLikelihoodBuffer
in aTask
.- The
PlaceLikelihoodBuffer
contains a list ofPlaceLikelihood
objects that represent likely places. For each place, the result includes the likelihood that the place is the right one. - The Places API includes the
PlacePicker
, which you use to include the place-picker UI in your app. The place-picker UI is a dialog that lets the user search for places and select places. - Launch the
PlacePicker
usingstartActivityForResult()
. Pass in anIntent
created withPlacePicker.IntentBuilder()
. - Retrieve the selected place in
onActivityResult()
by callingPlacePicker.getPlace()
. Pass in the activity context and the dataIntent
.
Related concept
The related concept documentation is in 8.1: Places API.
Learn more
Android developer documentation: