Exploring Google Play Services: Place Picker & Autocomplete

The Place Picker Widget and Autocomplete component are two powerful features part of Google Play Services. Here we take a look at both of these componenets and how we can implement them in our applications.

As of Play Services 7.0, Google have made some great features available for us to implement into our apps – two of which include the Place Picker UI widget and Autocomplete component. These can both help to greatly improve existing solutions within your applications, or even help you to implement such features into your app in the future. Either way, providing a native and clean solution to such commonly used features really help to boost the user experience provided by your application.

Seeing as I haven’t had a chance to implement either of these since they’ve been released, I thought it was about time to take a look.


Along with this blog post, I created a sample application to allow me to have a full experiment with these features – you can find the code on github here.


Setting up

As you’re still reading this, I presume that you’re wanting try out these components for yourself. So before you get your java hat on, you need to start by adding the play-services-location dependancy to your build.gradle file.

compile 'com.google.android.gms:play-services-location:8.1.0'

Note: Incase you aren’t aware of the format used for this statement, you only need to declare the dependancy of the play service that you require. Read here for more info.

Aside from this, you’ll also need to register for an API key in order to use the location play services, which can be done by following this guide. Once done, you need to add the API key and play services version declaration to your manifest as below:

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="YOUR_API_KEY" />
<meta-data android:name="com.google.android.gms.version"
           android:value="@integer/google_play_services_version" />

Place Picker

The place picker widget is a UI component which provides an interactive map using the current location of a device. This map can be used to select a nearby place, which can be done in one of two ways:

  • Selecting the current location – The user can move the displayed map pin to set their current location, if not satisfied with the pre-selected location.
  • Selecting a nearby place – The user can select a place from the list of nearby places based on their current location, this list can be dragged open from the bottom of the interactive map.

Users have two ways of selecting a ‘Place’ from the Place Picker widget. Dragging up the ‘Nearby places’ drawer will reveal a list of nearby locations

But why bother using this component?

  • Development time is greatly reduced – The component comes out-of-the-box, meaning that all features (map, places, lifecycle, network requests etc) are all handled completely by the widget. Compared to how this would be achieved in a bespoke approach, the development time reduces greatly.
  • Consistent user experience – Not only is this component used within Google apps, but third parties (like you!) will also hopefully be making use of this component. This means that your users are likely to be already familiar with how to operate the widget, reducing any new behaviours to learn when using your application.

Operational flow

So now we know exactly what it is we’re dealing with, it looks like we’re all ready to go. From launching to closing the Place Picker, there are several operations which take place.

  • The Place Picker is opened using startActivityForResult(), this is so that we can catch the selected place when our activity is returned to.
  • The next step is deciding the location to be used for the map. If a location is provided during the setup of the picker then this is used, otherwise by default the picker will use the devices current location.
  • The user then uses either the map or the nearby places list to make a selection, or closes the place picker without making a selection.
  • Once a selection is made, the place picker is finished and the result (the selected place) is returned to our activity.
The operational flow of the Place Picker makes it super simple to implement

Implementation

So to begin with we need to create a new builder instance using the IntentBuilder from the Place Picker class.

PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();

Using this builder, we can set the initial latitude-longitude bounds to be used by the place picker instance. As previously mentioned, if this is not set then by default our place picker will use the devices current location as the LatLngBounds.

builder.setLatLngBounds(new LatLngBounds(...));

Once the above is done we call launch the picker by using startActivityForResult(), passing our builder instance and request code as parameters. As previously mentioned, the selected Place from the Place Picker is returned to our activity within the onActivityResult callback, where we use the PLACE_PICKER_REQUEST request code to filter this result out.

startActivityForResult(builder.build(this), PLACE_PICKER_REQUEST);

When this startActivityForResult method is called, the place picker widget will open in a new activity. This is where the user will have the choice of either using their current location or selecting a place from the provided suggestions. Once the place picker is closed, our onActivityResult callback will be called:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == PLACE_PICKER_REQUEST) {
        if (resultCode == RESULT_OK) {
            Place selectedPlace = PlacePicker.getPlace(data, this);
            // Do something with the place
        }
    }
}

As shown above, we retrieve our selected Place when the activity is returned to. We retrieve the selected Place like so:

Place selectedPlace = PlacePicker.getPlace(data, this);

At this point, if the user uses the back button to leave the picker without selecting a place then there will be no result returned.

Note: You’ll need to grant the ACCESS_FINE_LOCATION permission in order to use the Place Picker widget.

Theming

In order to remain consistant with the theme of your application, by default the Place Picker widget will use the following colors found in your application theme:

  • colorPrimary – Used as the colour for the text and back arrow found in the action bar
  • colorPrimaryDark – Used for the background colour of the action bar

Because of this, please ensure that you’re setting these theme attributes.

Beacon signals

As of play services 7.8, the place picker widget will now also use signals from nearby beacons to determine the current location of the device. This works by using the PlaceId of the detected beacon(s) in order to combine it with other signals that are available to the device (network, wifi etc). This is then used to rank the suggestions which are displayed in the ‘Nearby Places’ drawer-list within the Place Picker component.

Place Autocomplete

Place Autocomplete is a new component that can be used to return a list of nearby place suggestions using a provided search query, where results are biased (but not restricted to) the current location of the device. This previously involved calling the places API directly, so now the same functionality can be achieved by using the GoogleApiClient wrapper to call the Places API for you.

To begin with, we’re required to create a new GoogleApiClient instance. We use the GoogleApiClient builder to create this, where we also are required to state that we wish to use the Places API:

mGoogleApiClient = new GoogleApiClient.Builder(this)
        .enableAutoManage(this, 0, this)
        .addApi(Places.GEO_DATA_API)
        .build();

Both the search query and our devices current location are used to make a request to the Auto Complete API using this instance.

Because RxJava is a part of my workflow now, I decided to implement this using observables. I suggest you check out the complete class to see all of the code, but the general flow of operations is as follows:

  • We begin by using the Places API to retrieve a list of Autocomplete Predictions based on the provided search query and latlng bounds.
PendingResult<AutocompletePredictionBuffer> results =
        Places.GeoDataApi.getAutocompletePredictions(
                             mGoogleApiClient, query, bounds, null);

Note: Providing a LatLngBounds instance does not restrict results to these bounds. Results are only biased to the provided bounds, meaning that places nearby will be prioritised in the results.

A PendingResult is a method used to retrieve data from a Google Play Services API call. We do this by using the await() method to give our PendingResult a specified time limit to return data to us.

AutocompletePredictionBuffer autocompletePredictions = 
                                results.await(60, TimeUnit.SECONDS);

Once we’ve waited for the result, we can then check our DataBuffer status to see if the request was a success:

final Status status = autocompletePredictions.getStatus();
if (!status.isSuccess()) {
    autocompletePredictions.release();
    subscriber.onError(null);
} else {
    for (AutocompletePrediction place : autocompletePredictions) {
        subscriber.onNext(
                new AutocompletePlace(
                        place.getPlaceId(),
                        place.getDescription()
                ));
    }
    autocompletePredictions.release();
    subscriber.onCompleted();
}

If successful, then we are returned a set of AutocompletePrediction objects, which in theory is just a minimal Place object. Alone this isn’t really enough for us to display useful information to the user, so we use the placeId to retrieve the full Place object from the API. This is carried out by chaining the request onto our observable flow, where we retrieve the place like so:

final PendingResult<PlaceBuffer> placeResult = 
               Places.GeoDataApi.getPlaceById(mGoogleApiClient, id);
placeResult.setResultCallback(new ResultCallback<PlaceBuffer>() {
    @Override
    public void onResult(PlaceBuffer places) {
        if (!places.getStatus().isSuccess()) {
            places.release();
            subscriber.onError(new Throwable(...));
        } else {
            subscriber.onNext(
                          PointOfInterest.fromPlace(places.get(0)));                
            places.close();                            
            subscriber.onCompleted();
        }
    }
});

Note: Be sure to always call release on the PlaceBuffer instance.

These Place results are then returned to our subscription in the activity and displayed in the search results recyclerview. This then works in a similar way to the Place Picker sample, when an autocomplete prediction is selected from the list it is returned to our MainActivity and displayed in the list of saved Places.

Conclusion

I’m super excited to use these components in future projects, they’re super easy to setup and flexible enough to fit into my current workflow. I hope from this article you can see for yourself just how easy it is to implement these services into your application(s).

Whilst I can still see the standard MapView being used in plenty of situations (mainly for it’s greater functionality and customisation), the Place Picker will come in handy for situations where you simply don’t require any extended features. The same applies to the Autocomplete component, again you are able to make a direct API call yourself to retrieve autocomplete suggestions (allowing finer tuning of results, as seen here). But using the GoogleApiClient as a wrapper again is enough when you don’t require the extensibility provided by the direct API calls. Saying this, I imagine the components from Play Services will be enough for most situations needed these features.

I’d love to hear your thoughts on these services, if you’ve got any comments and/or suggestions then feel free to leave a response or drop me a tweet!

If you enjoyed this article then please hit the recommend button!

Header Image credit: Sylwia Bartyzel

Leave a Reply

Your email address will not be published. Required fields are marked *