8.1: Using the Places API

Contents:

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:

  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.  A screenshot of the completed WalkMyAndroidPlaces app, with the business name and place type from the Places API

  2. Add a Pick a Place button that launches the place-picker UI, allowing the user to select a place.  The place-picker UI allows 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 your AndroidManifest.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:

  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:

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

  2. 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:
      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.
  3. Copy this value to your clipboard. You need it in the next set of steps.

  4. Download the starter code for this practical, WalkMyAndroidPlaces-Starter.
  5. 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.
Note: To protect your 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

  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
    
  4. 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
    
  5. 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.  A screenshot of the completed WalkMyAndroidPlaces app, with the business name and place type from the Places API

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'
    
  2. 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);
    
    Note: To use the PlaceDetectionClient interface, your app needs the ACCESS_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.

  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>
    
  3. 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);
    
  2. 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.
  3. Add an OnCompleteListener to the placeResult:
     placeResult.addOnCompleteListener
                         (new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
                     @Override
                     public void onComplete(@NonNull
                             Task<PlaceLikelihoodBufferResponse> task) {
                 });
    
  4. Create an if/else statement to check whether the Task was successful:
     if (task.isSuccessful()) {
     } else {
     {
    
  5. 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;
     }
    
  6. 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();
           }
       }
     }
    
  7. 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()));
     }
    
  8. After you use the place information, release the buffer:
     likelyPlaces.release();
    
  9. 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()));
     }
    
  10. 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);
     }
    
    Note: The setAndroidType() method is where you can add support for more place types. All you need to do is add another case to the switch statement and select the appropriate drawable. For a list of supported place types, see Place Types.
  2. In the onComplete() callback where you obtain the current Place object, call setAndroidType(). Pass in the currentPlace object.

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

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

  6. Run the WalkMyAndroid app on the emulator to start tracking the device location.

  7. 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).  The emulator location settings

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

 The place-picker UI allows the user to select a place.  A screenshot of the completed WalkMyAndroidPlaces app, with the business name and place type from the Places API

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();
     }
    
  3. 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.

  3. 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);
     }
    
  4. After you get the Place object, call setAndroidType(). Pass in your obtained object.
  5. 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()));
    
  6. 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.
Note: When the user selects a location using the 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

Note: All coding challenges are optional.

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

WalkMyAndroidPlaces-Solution

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 a PlaceLikelihoodBuffer in a Task.
  • The PlaceLikelihoodBuffer contains a list of PlaceLikelihood 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 using startActivityForResult(). Pass in an Intent created with PlacePicker.IntentBuilder().
  • Retrieve the selected place in onActivityResult() by calling PlacePicker.getPlace(). Pass in the activity context and the data Intent.

The related concept documentation is in 8.1: Places API.

Learn more

Android developer documentation:

results matching ""

    No results matching ""