This codelab is part of the Advanced Android Development training course, developed by the Google Developers Training team. You will get the most value out of this course if you work through the codelabs in sequence.

For complete details about the course, see the Advanced Android Development overview.

Introduction

The Location APIs can provide timely and accurate location information, but these APIs only return a set of geographic coordinates. In a previous codelab, 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:

What you'll learn

You will learn how to:

What you'll do

The app for this practical extends the WalkMyAndroid app from the previous practical in two ways:

  1. 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.

  1. Add a Pick a Place button that launches the place-picker UI, allowing the user to select a place.

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:

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:

For this practical, make sure that you use the debug certificate.

To view the debug certificate's fingerprint:

  1. Locate your debug keystore file, which is named debug.keystore. By default, the file is stored in the same directory as your Android Virtual Device (AVD) files. The file is created the first time you build your project:
  1. List the SHA-1 fingerprint:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore"\ -alias androiddebugkey -storepass android -keypass android

You should see output similar to the following:

Alias 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

The line that begins with SHA1 contains the certificate's SHA-1 fingerprint. The fingerprint is the sequence of 20 two-digit hexadecimal numbers separated by colons.

  1. Copy this value to your clipboard. You need it in the next set of steps.
  2. Download the starter code for this practical, WalkMyAndroidPlaces-Starter.
  3. Open Android Studio and open your AndroidManifest.xml file. Note the package string in the manifest tag. You need the package name in future steps.

1.2 Get an API key from the Google API Console

  1. Go to the Google API Console.
  2. Create or select a project. You can reuse the same project and API key for multiple apps.
  3. From the Dashboard page, click ENABLED APIS AND SERVICES. The API Library opens.
  4. Search for "Places" and select Google Places API for Android.
  5. Click ENABLE.
  6. If a warning appears telling you to create credentials, click Create credentials. Otherwise, open the Credentials page and click Create credentials.
  7. If you see a Find out what kind of credentials you need step, skip the prompts. Click directly on the API key link.
  8. Name the key whatever you like.

Restrict the key to Android apps:

  1. Follow the prompts to restrict the key to Android apps.
  2. Click Add package name and fingerprint.
  3. 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
  1. 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
  1. 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

  1. 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"/>
...

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.

  1. Add the following statement to your app-level build.gradle file. Replace XX.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'
  1. In the MainActivity, in the onCreate() method, initialize a PlaceDetectionClient object. You use this object to get information about the device's current location.
mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

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.

  1. In the MainActivity, create a String member variable called mLastPlaceName. This member variable will hold the name of the device's most probable location.
  2. In strings.xml, modify the address_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>
  1. In the onClick() method for the Start Tracking Location button, add another argument to the setText() call. Pass in the loading string so that the TextView label shows "Loading..." for the Name and Address lines:
mLocationTextView.setText(getString(R.string.address_text,
       getString(R.string.loading),// Name
       getString(R.string.loading),// Address
       new Date())); // Timestamp

When the AsyncTask returns an Address, the onTaskCompleted() method is called. In the onTaskComplete() method, obtain the current place name and update the TextView. To get the current place name, call PlaceDetectionClient.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.

  1. In the onTaskCompleted() method, call getCurrentPlace() on your PlaceDetectionClientApi instance. Store the result in a local variable. Because you are interested in all places, you can pass in null for the PlaceFilter:
Task<PlaceLikelihoodBufferResponse> placeResult =
                    mPlaceDetectionClient.getCurrentPlace(null);
  1. The getCurrentPlace() method call is underlined in Android Studio, because the method may throw a SecurityException if you don't have the right location permissions. Have the onTaskCompleted() method throw a SecurityException to remove the warning.
  2. Add an OnCompleteListener to the placeResult:
placeResult.addOnCompleteListener
                    (new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
                @Override
                public void onComplete(@NonNull
                        Task<PlaceLikelihoodBufferResponse> task) {
            });
  1. Create an if/else statement to check whether the Task was successful:
if (task.isSuccessful()) {
} else {
{
  1. If the Task was successful, call getResult() on the task to obtain the PlaceLikelihoodBufferResponse. Initialize an integer to hold the maximum value. Initialize a Place to hold the highest likelihood Place object.
if (task.isSuccessful()) {
  PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();
  float maxLikelihood = 0;
  Place currentPlace = null;
} 
  1. Iterate over each PlaceLikelihood object and check whether it has the highest likelihood so far. If it does, update the maxLikelihood and currentPlace 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();
      }
  }
} 
  1. If the currentPlace is not null, update the TextView with the result. The following code should all be within the if (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()));
}
  1. After you use the place information, release the buffer:
likelyPlaces.release();
  1. In the else block for the case where the Task 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()));
}
  1. 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.

  1. In MainActivity, create a setAndroidType() method that uses a Place object. Have the method assign the appropriate drawable to the ImageView, 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);
}
  1. In the onComplete() callback where you obtain the current Place object, call setAndroidType(). Pass in the currentPlace object.
  2. 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:

  1. 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.
  2. Start an emulator of your choice.
  3. 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.
  4. In the bottom right corner, click Load GPX/KML. Select the file you downloaded.

Five locations load in the GPS data playback window.

  1. 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.)

  1. Run the WalkMyAndroid app on the emulator to start tracking the device location.
  2. 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).

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:

  1. 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.
  2. Add a click handler that executes the following code to start the PlacePicker. Create an arbitrary constant called REQUEST_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();
}
  1. 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.

  1. To override the onActivityResult() method, select Code > Override Methods in Android Studio. Find and select the onActivityResult() method, then click OK.
  2. In onActivityResult(), create an if statement that checks whether the requestCode matches your request integer.

To get the Place object that was selected from the PlacePicker, use the PlacePicker.getPlace() method.

  1. If the resultCode is RESULT_OK, get the selected place from the data Intent using the PlacePicker.getPlace() method. Pass in the application context and the data Intent, which the system passes into onActivityResult().

If the resultCode isn't RESULT_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);
    }
  1. After you get the Place object, call setAndroidType(). Pass in your obtained object.
  2. Update the label TextView with the place name and address from the Place object. Set the update time to be the current time:
mLocationTextView.setText(
                    getString(R.string.address_text, place.getName(),
                            place.getAddress(), System.currentTimeMillis()));
  1. 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.

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 for coding challenge

WalkMyAndroidPlaces-Solution

The related concept documentation is in 8.1: Places API.

Android developer documentation:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Modify the WalkMyAndroidPlaces app to display a new Android image for shopping malls:

  1. Create a new Android image for shopping malls. (Hint: Use the Androdify tool.)
  2. If the place type is TYPE_SHOPPING_MALL, replace the Android image with your image.
  3. To test your app, pick a shopping mall from the PlacePicker dialog. Make sure that your new Android image is animated, and that TextView object is updated with the shopping mall's name and address.

Answer these questions

Question 1

Which class displays a dialog that allows a user to pick a place using an interactive map ?

Question 2

What are the two ways to add the autocomplete widget to your app?

Question 3

If your app uses the PlacePicker UI or the PlaceDetectionApi interface, what permission does your app require?

Submit your app for grading

Guidance for graders

Check that the app has the following features:

  1. When you tap the Pick a Place button, the place picker UI opens.
  2. When you pick a shopping mall on the map, the new Android image is displayed.
  3. The image is animated.
  4. The TextView updates to show the name and address of the shopping mall.

To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.