Android Places API: Autocomplete with getPlaceByID - Truiton
Skip to content

Android Places API: Autocomplete with getPlaceByID

Android Places API Autocomplete

A new API was released for Android, by the name of Google Places API. This API offers easy to use methods, through which Google Places data can be accessed on Android. The main advantage of this API is, while developing apps, you may not need to parse data from a web service. As all the data is exchanged through GoogleApiClient automatically. Google places API for android gives full access to Google’s database for places. Although this API is not completely free, and is offered under quota based plans, but still its a good trade off when compared to the earlier approach. Android Places API has many new methods for accessing Google places database, but here we will be discussing Autocomplete feature of it, to get a place Id. Further we will be using this unique Place ID to get place details by ID.

Earlier when we had to detect places around our current location in Android, we had to make an API call to Google Maps APIs for Places, with our current latitude and longitude. But now one of the greatest things about new Android Places API is that, to get places around your current place, you don’t need to pass LatLongs. As the new Android places API handles all of this, and much more by itself. If you wish to know in detail about more features of Google Places API for Android, please refer to this Android Places API tutorial, as here we are focused on getting a place Id using autocomplete feature of Android Places API.

Before starting this Android Places API with autocomplete and getPlaceById example, first include Google Play services in your app, by adding this in the dependencies section:

compile 'com.google.android.gms:play-services-places:10.2.1'

Add this in application tag of your manifest :

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

Add these permissions in the manifest:

<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.INTERNET"/>

For more details on using play services please refer to this setting up play services page. Next have a look at the steps to generate Places API for Android key

  1. Create a project in Google Developers console or use an existing one.
  2. Navigate to “APIs & Auth” section, select APIs.
  3. Search and select “Places API for Android”.
  4. Enable it.
  5. Then go to the credentials section.
  6. Create a key for Android application.
  7. Enter your SHA1 fingerprint with package name in the desired format. Use this SHA1 fingerprint tutorial to get your fingerprint.
  8. Paste the API key in a meta tag under the application tag of your Android Manifest as shown below.
<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="{YOUR_API_KEY}"/>

Android Places API – Autocomplete with Suggestions

As you might be aware, that Google’s Places API for Web has a very powerful autocomplete suggestions tool. But also it was not easy to integrate in an Android app. Now since Google has introduced Places API for Android with support for Autocomplete suggestions, things have gone a little easy. Place Autocomplete API has now been completely reinvented and supports many features like location boundaries, filtering place types and much more. Whats even better is that this API can be directly integrated into an Android AutoCompleteTextView, where Places API suggestions could be shown, like below.

Android Places API Autocomplete

Have a look at the layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context=".MainActivity">

    <AutoCompleteTextView
        android:id="@+id/autoCompleteTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:hint="Enter Place Here"/>

    <TextView
        android:id="@+id/header"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/autoCompleteTextView"
        android:layout_marginTop="20dp"
        android:text="Selected Place:"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/header"
        android:layout_marginTop="20dp"/>

    <TextView
        android:id="@+id/address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/name"/>

    <TextView
        android:id="@+id/place_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/address"/>

    <TextView
        android:id="@+id/phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/place_id"
        android:autoLink="phone"/>

    <TextView
        android:id="@+id/web"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/phone"
        android:autoLink="web"/>

    <TextView
        android:id="@+id/att"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:autoLink="web"/>

    <ImageView
        android:id="@+id/poweredBy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/att"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:src="@drawable/powered_by_google_light"/>

    <ImageView
        android:id="@+id/truiton_image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_above="@+id/poweredBy"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="-20dp"
        android:src="@mipmap/truiton_short_no_back"/>

</RelativeLayout>

Before making use of Android Places API for Autocomplete suggestions please remember to show the powered by Google attribution as per guidelines. Also please remember to show third party attributions (if any) as shown above.

Now before getting place id and details from an AutoCompleteTextView, we need to customize an ArrayAdapter which would be attached to this AutoCompleteTextView. This adapter would internally make calls to the Google Places API for suggestions by using GeoDataApi.getAutocompletePredictions() method. Have a look at the code:

package com.truiton.placeapiautocomplete;

import android.content.Context;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.Toast;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBuffer;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLngBounds;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

public class PlaceArrayAdapter
        extends ArrayAdapter<PlaceArrayAdapter.PlaceAutocomplete> implements Filterable {
    private static final String TAG = "PlaceArrayAdapter";
    private GoogleApiClient mGoogleApiClient;
    private AutocompleteFilter mPlaceFilter;
    private LatLngBounds mBounds;
    private ArrayList<PlaceAutocomplete> mResultList;

    /**
     * Constructor
     *
     * @param context  Context
     * @param resource Layout resource
     * @param bounds   Used to specify the search bounds
     * @param filter   Used to specify place types
     */
    public PlaceArrayAdapter(Context context, int resource, LatLngBounds bounds,
                             AutocompleteFilter filter) {
        super(context, resource);
        mBounds = bounds;
        mPlaceFilter = filter;
    }

    public void setGoogleApiClient(GoogleApiClient googleApiClient) {
        if (googleApiClient == null || !googleApiClient.isConnected()) {
            mGoogleApiClient = null;
        } else {
            mGoogleApiClient = googleApiClient;
        }
    }

    @Override
    public int getCount() {
        return mResultList.size();
    }

    @Override
    public PlaceAutocomplete getItem(int position) {
        return mResultList.get(position);
    }

    private ArrayList<PlaceAutocomplete> getPredictions(CharSequence constraint) {
        if (mGoogleApiClient != null) {
            Log.i(TAG, "Executing autocomplete query for: " + constraint);
            PendingResult<AutocompletePredictionBuffer> results =
                    Places.GeoDataApi
                            .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                    mBounds, mPlaceFilter);
            // Wait for predictions, set the timeout.
            AutocompletePredictionBuffer autocompletePredictions = results
                    .await(60, TimeUnit.SECONDS);
            final Status status = autocompletePredictions.getStatus();
            if (!status.isSuccess()) {
                Toast.makeText(getContext(), "Error: " + status.toString(),
                        Toast.LENGTH_SHORT).show();
                Log.e(TAG, "Error getting place predictions: " + status
                        .toString());
                autocompletePredictions.release();
                return null;
            }

            Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
                    + " predictions.");
            Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
            ArrayList resultList = new ArrayList<>(autocompletePredictions.getCount());
            while (iterator.hasNext()) {
                AutocompletePrediction prediction = iterator.next();
                resultList.add(new PlaceAutocomplete(prediction.getPlaceId(),
                        prediction.getFullText(null)));
            }
            // Buffer release
            autocompletePredictions.release();
            return resultList;
        }
        Log.e(TAG, "Google API client is not connected.");
        return null;
    }

    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults results = new FilterResults();
                if (constraint != null) {
                    // Query the autocomplete API for the entered constraint
                    mResultList = getPredictions(constraint);
                    if (mResultList != null) {
                        // Results
                        results.values = mResultList;
                        results.count = mResultList.size();
                    }
                }
                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    // The API returned at least one result, update the data.
                    notifyDataSetChanged();
                } else {
                    // The API did not return any results, invalidate the data set.
                    notifyDataSetInvalidated();
                }
            }
        };
        return filter;
    }

    class PlaceAutocomplete {

        public CharSequence placeId;
        public CharSequence description;

        PlaceAutocomplete(CharSequence placeId, CharSequence description) {
            this.placeId = placeId;
            this.description = description;
        }

        @Override
        public String toString() {
            return description.toString();
        }
    }
}

The above code simply calls the GeoDataApi.getAutocompletePredictions() method to get the Places API autocomplete suggestions, on change of every character. This API call also supports a timeout limit by using the await(60, TimeUnit.SECONDS) method, where the first argument is used to specify the limit. One of the main things to be noted in the ArrayAdapter above is that, an inner class PlaceAutocomplete is being used to store and return the description and IDs of the places to main activity where the Android Places API would be called once again for selected ID.

Android Places API – Get Place by ID

Another API which is offered under the Google Places API for Android suite is Place Details API. This API is also called using the GoogleApiClient, but a place ID is required to make the call. Using GeoDataApi.getPlaceById method we can get all the details of requested place. I would like to remind once again here that, calling this API is not completely free, please refer to this usage limits page. Now lets have a look at the main activity class where both these Autocomplete and Place Details by ID APIs are used in a complementing way.

package com.truiton.placeapiautocomplete;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.PlaceBuffer;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;


public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.OnConnectionFailedListener,
        GoogleApiClient.ConnectionCallbacks {
    private static final String LOG_TAG = "MainActivity";
    private static final int GOOGLE_API_CLIENT_ID = 0;
    private AutoCompleteTextView mAutocompleteTextView;
    private TextView mNameTextView;
    private TextView mAddressTextView;
    private TextView mIdTextView;
    private TextView mPhoneTextView;
    private TextView mWebTextView;
    private TextView mAttTextView;
    private GoogleApiClient mGoogleApiClient;
    private PlaceArrayAdapter mPlaceArrayAdapter;
    private static final LatLngBounds BOUNDS_MOUNTAIN_VIEW = new LatLngBounds(
            new LatLng(37.398160, -122.180831), new LatLng(37.430610, -121.972090));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGoogleApiClient = new GoogleApiClient.Builder(MainActivity.this)
                .addApi(Places.GEO_DATA_API)
                .enableAutoManage(this, GOOGLE_API_CLIENT_ID, this)
                .addConnectionCallbacks(this)
                .build();
        mAutocompleteTextView = (AutoCompleteTextView) findViewById(R.id
                .autoCompleteTextView);
        mAutocompleteTextView.setThreshold(3);
        mNameTextView = (TextView) findViewById(R.id.name);
        mAddressTextView = (TextView) findViewById(R.id.address);
        mIdTextView = (TextView) findViewById(R.id.place_id);
        mPhoneTextView = (TextView) findViewById(R.id.phone);
        mWebTextView = (TextView) findViewById(R.id.web);
        mAttTextView = (TextView) findViewById(R.id.att);
        mAutocompleteTextView.setOnItemClickListener(mAutocompleteClickListener);
        mPlaceArrayAdapter = new PlaceArrayAdapter(this, android.R.layout.simple_list_item_1,
                BOUNDS_MOUNTAIN_VIEW, null);
        mAutocompleteTextView.setAdapter(mPlaceArrayAdapter);
    }

    private AdapterView.OnItemClickListener mAutocompleteClickListener
            = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final PlaceArrayAdapter.PlaceAutocomplete item = mPlaceArrayAdapter.getItem(position);
            final String placeId = String.valueOf(item.placeId);
            Log.i(LOG_TAG, "Selected: " + item.description);
            PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
                    .getPlaceById(mGoogleApiClient, placeId);
            placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
            Log.i(LOG_TAG, "Fetching details for ID: " + item.placeId);
        }
    };

    private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
            = new ResultCallback<PlaceBuffer>() {
        @Override
        public void onResult(PlaceBuffer places) {
            if (!places.getStatus().isSuccess()) {
                Log.e(LOG_TAG, "Place query did not complete. Error: " +
                        places.getStatus().toString());
                return;
            }
            // Selecting the first object buffer.
            final Place place = places.get(0);
            CharSequence attributions = places.getAttributions();

            mNameTextView.setText(Html.fromHtml(place.getName() + ""));
            mAddressTextView.setText(Html.fromHtml(place.getAddress() + ""));
            mIdTextView.setText(Html.fromHtml(place.getId() + ""));
            mPhoneTextView.setText(Html.fromHtml(place.getPhoneNumber() + ""));
            mWebTextView.setText(place.getWebsiteUri() + "");
            if (attributions != null) {
                mAttTextView.setText(Html.fromHtml(attributions.toString()));
            }
        }
    };

    @Override
    public void onConnected(Bundle bundle) {
        mPlaceArrayAdapter.setGoogleApiClient(mGoogleApiClient);
        Log.i(LOG_TAG, "Google Places API connected.");

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.e(LOG_TAG, "Google Places API connection failed with error code: "
                + connectionResult.getErrorCode());

        Toast.makeText(this,
                "Google Places API connection failed with error code:" +
                        connectionResult.getErrorCode(),
                Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionSuspended(int i) {
        mPlaceArrayAdapter.setGoogleApiClient(null);
        Log.e(LOG_TAG, "Google Places API connection suspended.");
    }
}

This would result in a screen like this:

Android Places API Get Place Id

Now lets understand, how to get place suggestions from the API, in an AutoCompleteTextView using the GeoDataApi.getAutocompletePredictions() method and further displaying the detailed information using GeoDataApi.getPlaceById method on screen. To get full understanding on how this works have a look at code sample repository below:

Source Code

As you can see the code above, first an instance of GoogleApiClient is created. Further it is set in the adapter as soon as the connection is established using GoogleApiClient.ConnectionCallbacks interface. Next the PlaceArrayAdapter is set using LatLngBounds. A class used for defining latitude and longitude bounds. Here in the Android Places API Autocomplete example above, you can see that I have been using the latitudes and longitudes of Mountain View to set the LatLngBounds. Now to get place details we need to call getPlaceById method in the onItemClick method of AdapterView.OnItemClickListener, which is set while creating the activity. This as a whole would successfully show Google Places API autocomplete predictions in the AutocompleteTextView. But if you would like to use a widget for Autocomplete predictions, refer to this tutorial. Hope this helps. Connect with us on Facebook, Google+ and Twitter for more updates.

33 thoughts on “Android Places API: Autocomplete with getPlaceByID”

  1. Hi,

    thank for your good work. I just have a simple question I want to add an AutoCompleteFilter to the method, for example I want just the result of airports.

    how I can accomplish this, could you give me an example???

  2. Great tutorial

    However when I type text really quickly in the AutoCompleteTextView I get the error:

    java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(-1, class android.widget.ListPopupWindow$DropDownListView) with Adapter(class com.mycompany.appointmentmanager.PlaceArrayAdapter)]

    Any suggestions on a solutions?

  3. Hi Sir,
    Thanks for your Best Tutorial, and you save my Job sir. I am reading many tutorial for Google Place API with Autocomplete TextView but error occur after one day Trying your tutorial, my problem is solved……

    So again thank you sir..

  4. Hello, when i have use this code in eclipse with latest google api then my eclipse is hung at 100% and not run , have you any solution for this

  5. Hi great tutorial, i have a question : How to add the autocompleteText in action bar in this case ? and how to animate maps to location that is selected by the user in the search ?

  6. hi,

    If my location is chennai, I need those areas in the first order. kindly let me know how to do like this?? Based on my location which i enter. sorry for my English. pls let me know.

  7. hello!
    thank you for your awesome work. i tried to run your code but it says that the getDescription() method on line 92 of the ‘placeArrayAdapar.java’ file has been deprecated. i tried to use getFullText() instead but i could get around it. i am very much of a beginner in android. Any help in this section would be appreciated.

  8. Thank you for this detailed example…it’s exactly what I’ve been looking for. I have one question though. In this block of code:

    Iterator iterator = autocompletePredictions.iterator();
    ArrayList resultList = new ArrayList(autocompletePredictions.getCount());
    while (iterator.hasNext()) {
    AutocompletePrediction prediction = iterator.next();
    resultList.add(new PlaceAutocomplete(prediction.getPlaceId(),
    prediction.getDescription()));
    }

    Specifically,
    resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getDescription()));

    I get the compilation error: PlaceAutoComplete has private access in com.google.android.gms.location.places.ui.PlaceAutocomplete;

    I can’t instantiate PlaceAutoComplete, and I can’t find any references to my problem.

    Here’s my import: import com.google.android.gms.location.places.ui.PlaceAutocomplete;

    Here’s my gradle dependency: compile ‘com.google.android.gms:play-services-places:9.6.1’

    Do you have any ideas on what might be the cause of my compile issue?
    Thx.

  9. AutocompletePrediction class does not contain any method like getDescription so “prediction.getDescription()” is giving an error . Can you please describe, what this method do?

  10. Log.i(LOG_TAG, “Selected: ” + item.description);
    How to split item.description to Address line one, two, three, city, state, country, countrycode, postal code.
    Or how to get Address line one, two, three, city, state, country, countrycode, postal code using placeId .

  11. Thank you for the great work!
    However, I always end up getting Status{statusCode=ERROR, resolution=null}.
    @Mohit, can you help me with that?

  12. I get the following error while open the Activity at Second time
    java.lang.IllegalStateException: Already managing a GoogleApiClient with id 0
    please help me

Leave a Reply

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