Migrating to the New Places SDK Client

The Places SDK for Android introduces an all-new static library with updated functionality. The Google Play Services version of the Places SDK for Android (in Google Play Services 16.0.0) is deprecated as of January 29, 2019, and will be turned off on July 29, 2019. A new version of the Places SDK for Android is now available. We recommend updating to the new version as soon as possible. This guide explains the changes, and shows you how to update your projects to use the new version of the Places SDK for Android.

What's changed?

The main areas of change are as follows:

  • The new version of the Places SDK for Android is distributed as a static client library. Until now, the Places SDK for Android has been made available through Google Play Services.
  • A compatibility library is available, which enables you to switch from the deprecated Google Play Services Places library to the new static library with minimal effort.
  • There are all-new methods.
  • Field masks are now supported for methods that return place details. You can use field masks to specify which types of place data to return.
  • The exceptions used to report errors have been improved.
  • Autocomplete now supports session tokens.
  • The Place Picker is deprecated.

Install the client library

The new version of the Places SDK for Android is distributed as a static client library. Until now, the Places SDK for Android has been made available through Google Play Services.

You can use Maven to add the Places SDK for Android client library to your Android Studio project. Take the following steps to install the client library:

  1. In the dependencies section of your app-level build.gradle file, add a dependency for the new SDK client library, as shown in the following example:

    dependencies {
      implementation 'com.google.android.libraries.places:places:1.1.0'
    }
  2. Sync your Gradle project.

  3. Set the minSdkVersion for your application project to 16 or higher.

  4. Update your "Powered by Google" assets:

    @drawable/powered_by_google_light // OLD
    @drawable/places_powered_by_google_light // NEW
    
    @drawable/powered_by_google_dark // OLD
    @drawable/places_powered_by_google_dark // NEW
    

Initialize the new Places SDK client

Initialize the new Places SDK client as shown in the following example:

// Add an import statement for the client library.
import com.google.android.libraries.places.api.Places;

...

// Initialize Places.
Places.initialize(getApplicationContext(), apiKey);

// Create a new Places client instance.
PlacesClient placesClient = Places.createClient(this);

Install the compatibility library

The compatibility library is a thin library that mirrors the API surface of the current Google Play Services Places client, and wraps the new Places SDK client. There is no need to alter the basic functionality of your app, as it will function identically to the previous version with only a few minor changes. The compatibility library is provided to assist with your migration to the new Places SDK client.

  • The compatibility library only supports newer Task-based APIs, and will not work with the earlier PendingResult model.
  • The Place Picker is only supported in the compatibility library, NOT in the new client library. To continue using the Place Picker, you must install the compatibility library. If you have enabled the previous Places SDK for Android service in the console, do NOT disable it! You must also enable the new API services in the console if you're using them.

You can use Maven to add the Places SDK for Android compatibility library to your Android Studio project. Take the following steps to install the compatibility library:

  1. In the dependencies section of your app-level build.gradle file, add a dependency for the compatibility library, as shown in the following example:

    dependencies {
      implementation 'com.google.android.libraries.places:places-compat:1.1.0'
    }
  2. In the dependencies section of your app-level build.gradle file, remove the dependency for play-services-places:

    implementation 'com.google.android.gms:play-services-places:16.0.0'
  3. Sync your Gradle project.

  4. Find and replace all gms Places packages to their Compat equivalents. For example:

    import com.google.android.gms.location.places.Place; // OLD
    import com.google.android.libraries.places.compat.Place; // NEW,
    
  5. Update your "Powered by Google" assets:

    @drawable/powered_by_google_light // OLD
    @drawable/places_powered_by_google_light // NEW
    
    @drawable/powered_by_google_dark // OLD
    @drawable/places_powered_by_google_dark // NEW
    
  6. Set the minSdkVersion for your application project to 16 or higher.

Compatibility script

A compatibility script which automates the conversion process has been provided. It automatically does the following steps for you:

  • Updates your project's Gradle dependencies to use the compatibility library.
  • Updates your "Powered by Google" assets.
  • Replaces import statements for the previous version with their compatibility library equivalents.

To run the script:

  1. Copy the contents of places_compat_compatify.sh, and save as a file to your local computer.
  2. Use the following command to run the compatibility script (be sure to use a relative project path (for example /java/com/google/...):

    ./places_compat_compatify.sh <path-to-project> 1.1.0
    

Status codes

The status code for QPS limit errors has changed. QPS limit errors are now returned via PlaceStatusCodes.OVER_QUERY_LIMIT. There are no more QPD limits.

The following status codes have been added:

  • REQUEST_DENIED — The request was denied. Possible reasons for this include:

    • No API key was provided.
    • An invalid API key was provided.
    • The Places API has not been enabled in the Cloud console.
    • An API key was provided with incorrect key restrictions.
  • INVALID_REQUEST — The request is invalid due to a missing or invalid argument.

  • NOT_FOUND — No result was found for the given request.

New methods

The new version of the Places SDK for Android introduces all-new methods, which have been designed for consistency. All of the new methods adhere to the following:

  • Endpoints no longer use the get verb.
  • Request and response objects share the same name as the corresponding client method.
  • Request objects now have builders; required params are passed as request builder params.
  • Buffers are no longer used.

This section introduces the new methods, and shows you how they work.

Fetch a place by ID

Use fetchPlace() to get details about a particular place. fetchPlace() functions similarly to getPlaceById().

Follow these steps to fetch a place:

  1. Call fetchPlace(), passing a FetchPlaceRequest object specifying a Place ID and a list of fields specifying the Place data to return.

    // Define a Place ID.
    String placeId = "INSERT_PLACE_ID_HERE";
    
    // Specify the fields to return.
    List<Place.Field> placeFields = Arrays.asList(Place.Field.ID, Place.Field.NAME);
    
    // Construct a request object, passing the place ID and fields array.
    FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields)
            .build();
    
    
  2. Call addOnSuccessListener() to handle the FetchPlaceResponse. A single Place result is returned.

    // Add a listener to handle the response.
    placesClient.fetchPlace(request).addOnSuccessListener((response) -> {
      Place place = response.getPlace();
      Log.i(TAG, "Place found: " + place.getName());
    }).addOnFailureListener((exception) -> {
        if (exception instanceof ApiException) {
            ApiException apiException = (ApiException) exception;
            int statusCode = apiException.getStatusCode();
            // Handle error with given status code.
            Log.e(TAG, "Place not found: " + exception.getMessage());
        }
    });
    

Fetch a place photo

Use fetchPhoto() to get a place photo. fetchPhoto() returns photos for a place. The pattern for requesting a photo has been simplified. You can now request PhotoMetadata directly from the Place object; a separate request is no longer necessary. Photos can have a maximum width or height of 1600px. fetchPhoto() functions similarly to getPhoto().

Follow these steps to fetch place photos:

  1. Set up a call to fetchPlace(). Be sure to include the PHOTO_METADATAS field in your request:

    List<Place.Field> fields = Arrays.asList(Place.Field.PHOTO_METADATAS);
    
  2. Get a Place object (this example uses fetchPlace(), but you can also use findCurrentPlace()):

    FetchPlaceRequest placeRequest = FetchPlaceRequest.builder(placeId, fields).build();
    
  3. Add an OnSuccessListener to get the photo metadata from the resulting Place in the FetchPlaceResponse, then use the resulting photo metadata to get a bitmap and attribution text:

    placesClient.fetchPlace(placeRequest).addOnSuccessListener((response) -> {
        Place place = response.getPlace();
    
        // Get the photo metadata.
        PhotoMetadata photoMetadata = place.getPhotoMetadatas().get(0);
    
        // Get the attribution text.
        String attributions = photoMetadata.getAttributions();
    
        // Create a FetchPhotoRequest.
        FetchPhotoRequest photoRequest = FetchPhotoRequest.builder(photoMetadata)
                .setMaxWidth(500) // Optional.
                .setMaxHeight(300) // Optional.
                .build();
        placesClient.fetchPhoto(photoRequest).addOnSuccessListener((fetchPhotoResponse) -> {
            Bitmap bitmap = fetchPhotoResponse.getBitmap();
            imageView.setImageBitmap(bitmap);
        }).addOnFailureListener((exception) -> {
            if (exception instanceof ApiException) {
                ApiException apiException = (ApiException) exception;
                int statusCode = apiException.getStatusCode();
                // Handle error with given status code.
                Log.e(TAG, "Place not found: " + exception.getMessage());
            }
        });
    });
    

Find a place from the user's location

Use findCurrentPlace() to find the current location of the user's device. findCurrentPlace() returns a list of PlaceLikelihoods indicating places where the user's device is most likely to be located. findCurrentPlace() functions similarly to getCurrentPlace().

Follow these steps to get the current location of the user's device:

  1. Make sure your app requests the ACCESS_FINE_LOCATION and ACCESS_WIFI_STATE permissions. The user must grant permission to access their current device location. See Request App Permissions for details.

  2. Create a FindCurrentPlaceRequest, including a list of place data types to return.

      // Use fields to define the data types to return.
      List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME);
    
      // Use the builder to create a FindCurrentPlaceRequest.
      FindCurrentPlaceRequest request =
              FindCurrentPlaceRequest.builder(placeFields).build();
    
  3. Call findCurrentPlace and handle the response, checking first to verify that the user has granted permission to use their device location.

      // Call findCurrentPlace and handle the response (first check that the user has granted permission).
      if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
          placesClient.findCurrentPlace(request).addOnSuccessListener(((response) -> {
              for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                  Log.i(TAG, String.format("Place '%s' has likelihood: %f",
                          placeLikelihood.getPlace().getName(),
                          placeLikelihood.getLikelihood()));
                  textView.append(String.format("Place '%s' has likelihood: %f\n",
                          placeLikelihood.getPlace().getName(),
                          placeLikelihood.getLikelihood()));
              }
          })).addOnFailureListener((exception) -> {
              if (exception instanceof ApiException) {
                  ApiException apiException = (ApiException) exception;
                  Log.e(TAG, "Place not found: " + apiException.getStatusCode());
              }
          });
      } else {
          // A local method to request required permissions;
          // See https://developer.android.com/training/permissions/requesting
          getLocationPermission();
      }
    

Find autocomplete predictions

Use findAutocompletePredictions() to return place predictions in response to user search queries. findAutocompletePredictions() functions similarly to getAutocompletePredictions().

The following example shows calling findAutocompletePredictions():

// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
// Create a RectangularBounds object.
RectangularBounds bounds = RectangularBounds.newInstance(
  new LatLng(-33.880490, 151.184363),
  new LatLng(-33.858754, 151.229596));
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
// Call either setLocationBias() OR setLocationRestriction().
   .setLocationBias(bounds)
   //.setLocationRestriction(bounds)
   .setCountry("au")
   .setTypeFilter(TypeFilter.ADDRESS)
   .setSessionToken(token)
   .setQuery(query)
   .build();

placesClient.findAutocompletePredictions(request).addOnSuccessListener((response) -> {
   for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {
       Log.i(TAG, prediction.getPlaceId());
       Log.i(TAG, prediction.getPrimaryText(null).toString());
   }
}).addOnFailureListener((exception) -> {
   if (exception instanceof ApiException) {
       ApiException apiException = (ApiException) exception;
       Log.e(TAG, "Place not found: " + apiException.getStatusCode());
   }
});

Session tokens

Session tokens group the query and selection phases of a user search into a discrete session for billing purposes. We recommend using session tokens for all autocomplete sessions. The session begins when the user starts typing a query, and concludes when they select a place. Each session can have multiple queries, followed by one place selection. Once a session has concluded, the token is no longer valid; your app must generate a fresh token for each session.

Field masks

In methods that return place details, you must specify which types of place data to return with each request. This helps to ensure that you only request (and pay for) data that you will actually use.

To specify which data types to return, pass an array of Place.Fields in your FetchPlaceRequest, as shown in the following example:

// Include address, ID, and phone number.
List<Place.Field> placeFields = Arrays.asList(Place.Field.ADDRESS,
                                              Place.Field.ID,
                                              Place.Field.PHONE_NUMBER);

You can use one or more of the following fields:

  • Place.Field.ADDRESS
  • Place.Field.ID
  • Place.Field.LAT_LNG
  • Place.Field.NAME
  • Place.Field.OPENING_HOURS
  • Place.Field.PHONE_NUMBER
  • Place.Field.PHOTO_METADATAS
  • Place.Field.PLUS_CODE
  • Place.Field.PRICE_LEVEL
  • Place.Field.RATING
  • Place.Field.TYPES
  • Place.Field.USER_RATINGS_TOTAL
  • Place.Field.VIEWPORT
  • Place.Field.WEBSITE_URI

Read more about Places Data SKUs.

Place Picker and Autocomplete updates

This section explains the changes to the Places widgets (Place Picker and Autocomplete).

Programmatic autocomplete

The following changes were made to autocomplete:

  • PlaceAutocomplete is renamed to Autocomplete.
    • PlaceAutocomplete.getPlace is renamed to Autocomplete.getPlaceFromIntent.
    • PlaceAutocomplete.getStatus is renamed to Autocomplete.getStatusFromIntent.
  • PlaceAutocomplete.RESULT_ERROR is renamed to AutocompleteActivity.RESULT_ERROR (error handling for the autocomplete fragment has NOT changed).

Place Picker

The Place Picker is deprecated as of January 29, 2019. This feature will be turned off on July 29, 2019, and will no longer be available after that date. You must install the compatibility library to continue using the Place Picker during the deprecation period. Once the deprecation period has ended, the Place Picker will no longer be available for use (including the version in the compatibility library).

Autocomplete widgets

The autocomplete widgets have been updated:

  • The Place prefix has been removed from all classes.
  • Added support for session tokens. The widget manages tokens for you automatically in the background.
  • Added support for field masks, which let you choose which types of place data to return after the user makes a selection.

The following sections show how to add an autocomplete widget to your project.

Embed an AutocompleteFragment

To add an autocomplete fragment, take the following steps:

  1. Add a fragment to your activity's XML layout, as shown in the following example.

    <fragment
      android:id="@+id/autocomplete_fragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:name=
    "com.google.android.libraries.places.widget.AutocompleteSupportFragment"
      />
    
  2. To add the autocomplete widget to the activity, take these steps:

    • Initialize Places, passing the application context and your API key.
    • Initialize the AutocompleteSupportFragment.
    • Call setPlaceFields() to indicate the types of place data that you want to get.
    • Add a PlaceSelectionListener to do something with the result, as well as handle any errors that might occur.

    The following example shows adding an autocomplete widget to an activity:

    /**
     * Initialize Places. For simplicity, the API key is hard-coded. In a production
     * environment we recommend using a secure mechanism to manage API keys.
     */
    if (!Places.isInitialized()) {
        Places.initialize(getApplicationContext(), "YOUR_API_KEY");
    }
    
    // Initialize the AutocompleteSupportFragment.
    AutocompleteSupportFragment autocompleteFragment = (AutocompleteSupportFragment)
            getSupportFragmentManager().findFragmentById(R.id.autocomplete_fragment);
    
    autocompleteFragment.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.NAME));
    
    autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
        @Override
        public void onPlaceSelected(Place place) {
            // TODO: Get info about the selected place.
            Log.i(TAG, "Place: " + place.getName() + ", " + place.getId());
        }
    
        @Override
        public void onError(Status status) {
            // TODO: Handle the error.
            Log.i(TAG, "An error occurred: " + status);
        }
    });
    

Use an intent to launch the autocomplete activity

  1. Initialize Places, passing app context and your API key
  2. Use Autocomplete.IntentBuilder to create an intent, passing the desired PlaceAutocomplete mode (full-screen, or overlay). The intent must call startActivityForResult, passing in a request code that identifies your intent.
  3. Override the onActivityResult callback to receive the selected place.

The following example shows you how to use an intent to launch autocomplete, and then handle the result:

    /**
     * Initialize Places. For simplicity, the API key is hard-coded. In a production
     * environment we recommend using a secure mechanism to manage API keys.
     */
    if (!Places.isInitialized()) {
        Places.initialize(getApplicationContext(), "YOUR_API_KEY");
    }

    ...

    // Set the fields to specify which types of place data to return.
    List<Place.Field> fields = Arrays.asList(Place.Field.ID, Place.Field.NAME);

    // Start the autocomplete intent.
    Intent intent = new Autocomplete.IntentBuilder(
            AutocompleteActivityMode.FULLSCREEN, fields)
            .build(this);
    startActivityForResult(intent, AUTOCOMPLETE_REQUEST_CODE);

    ...

    /**
     * Override the activity's onActivityResult(), check the request code, and
     * do something with the returned place data (in this example it's place name and place ID).
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                Place place = Autocomplete.getPlaceFromIntent(data);
                Log.i(TAG, "Place: " + place.getName() + ", " + place.getId());
            } else if (resultCode == AutocompleteActivity.RESULT_ERROR) {
                // TODO: Handle the error.
                Status status = Autocomplete.getStatusFromIntent(data);
                Log.i(TAG, status.getStatusMessage());
            } else if (resultCode == RESULT_CANCELED) {
                // The user canceled the operation.
            }
        }
    }

Place Picker is deprecated

The Place Picker is deprecated as of January 29, 2019. This feature will be turned off on July 29, 2019, and will no longer be available after that date. To continue using the Place Picker through the deprecation period, do NOT disable the Places SDK for Android in your Google Cloud Platform project, as doing so will also disable the Place Picker.

Appendix: Compatibility script source code

Use the following script to configure your Android Studio project to use the compatibility library:

#!/usr/bin/env bash

# Run this script to convert an Android Studio project from using the old Google Play Services
# Places library to the new Standalone Places Compat Library.
#  $1 = Directory to the Android Studio project
#  $2 = Maven version of the Compat Library to use, e.g 1.0.0

# Recursively replaces all occurrences of a given string in the specified files.
# Files in the build, .idea, .git, and .svn dirs are ignored.
#  $1 = Directory to run findReplace on.
#  $2 = Regexp expression of filename to run findReplace on.
#  $3 = Source string to search for (may be regexp).
#  $4 = Replacement string.
function findReplace {
  find "${1}" -type f -regex "${2}" -not -regex ".*/\(build\|\.idea\|\.git\|\.svn\)/.*" | xargs sed -i "s/${3}/${4}/g"
}

if [[ "$#" -ne 2 ]]; then
    echo -e "ERROR: Invalid parameters, excepted parameters:\n\t$0 <path to project> <compat version>"
    exit 1
fi

GRADLE_FILE_REGEXP=".*/build\.gradle"
JAVA_FILES_REGEXP=".*\.java"
XML_FILES_REGEXP=".*\.xml"
JAVA_AND_XML_FILES_REGEXP=".*\.\(java\|xml\)"

# 1. Replace the Google Play Services Places client in gradle file with the Compat library.
OLD_PLACES_DEPENDENCY="com.google.android.gms:play-services-places:[0-9]*.[0-9]*.[0-9]*"
NEW_COMPAT_PLACES_DEPENDENCY="com.google.android.libraries.places:places-compat:${2}"
findReplace "${1}" "${GRADLE_FILE_REGEXP}" "${OLD_PLACES_DEPENDENCY}" "${NEW_COMPAT_PLACES_DEPENDENCY}"

# 2. Find and replace all Google Play Services Places packages with the Compat equivalents.
OLD_PLACES_PACKAGE="com.google.android.gms.location.places"
NEW_COMPAT_PLACES_PACKAGE="com.google.android.libraries.places.compat"
findReplace "${1}" "${JAVA_AND_XML_FILES_REGEXP}" "${OLD_PLACES_PACKAGE}" "${NEW_COMPAT_PLACES_PACKAGE}"

# 3. Update the powered_by_google assets:
# matches: R.drawable.powered_by_google_dark (and light)
OLD_PLACES_ASSET_JAVA="\(R\.drawable\.\)\(powered_by_google_\)\(light\|dark\)\([^a-zA-Z0-9_\$]\)"
NEW_PLACES_ASSET_JAVA="\1places_\2\3\4"
findReplace "${1}" "${JAVA_FILES_REGEXP}" "${OLD_PLACES_ASSET_JAVA}" "${NEW_PLACES_ASSET_JAVA}"

# matches: "@drawable/powered_by_google_dark" (and light)
OLD_PLACES_ASSET_XML="\"\(@drawable\/\)\(powered_by_google_\)\(light\|dark\)\""
NEW_PLACES_ASSET_XML="\"\1places_\2\3\""
findReplace "${1}" "${XML_FILES_REGEXP}" "${OLD_PLACES_ASSET_XML}" "${NEW_PLACES_ASSET_XML}"

发送以下问题的反馈:

此网页
Places SDK for Android