ZOFTINO.COM android and web dev tutorials

Google Places Auto Complete Android

Using Google places API in android apps, you can develop features related to places in your app. Google places API allows you to find places near a location and get details of a selected place. Google place API contains PlacePicker and Autocomplete UI widgets and GeoDataClient and PlaceDetectionClient APIs.

In this post, you can learn how to create place search functionality in your app using Autocomplete service which is available as part of Google places API. Place search functionality can be implemented using Autocomplet UI wizard or by creating custom auto complete component, this post covers both.

To know how to get a list of places near current location of a device, please see nearby places PlaceDetectionClient API example. To know how to use PlacePicker in your app to allow user to pick a place on map, please read Google place picker android example

Project Setup

To use Google places API, you need to create a project in Google developer console, enable API, create Key, add key to android manifest xml file and add libraries to build.gradle files, for detailed instructions on how to setup your project to use Places API, please see nearby places Android example.

Autocomplete UI widget

As mentioned before, Autocomplete UI widget can be used to provide search feature which displays auto complete help as user types in text to find places. To show Autocomplete UI widget, you need to create an intent using PlaceAutocomplete.IntentBuilder and start activity as show below. Autocomplete UI widget can be displayed either in full screen mode or overlay mode.

try {
    Intent intent =
            new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
                    .build(this);
    startActivityForResult(intent, AUTO_COMP_REQ_CODE);
} catch (Exception e) {
    Log.e(TAG, e.getStackTrace().toString());
} 

Once user selects a place from auto complete list, you can capture the selection in onActivityResult method in the activity which started Autocomplete widget.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if(requestCode == AUTO_COMP_REQ_CODE){
        if (resultCode == RESULT_OK) {
            Place place = PlaceAutocomplete.getPlace(this, data);
            Toast.makeText(this, "place "+place.toString(),
                    Toast.LENGTH_LONG).show();
        }
    }
} 

You can make auto complete service return places in a particular area first before it returns places from other areas by setting bounds bias. You can set bound bias by calling setBoundsBias method on PlaceAutocomplete.IntentBuilder object and passing LatLngBounds object as shown below. By default priority is given to places near device’s current location.

 LatLngBounds latLngBounds = new LatLngBounds(
	new LatLng(47.64299816, -122.14351988),
        new LatLng(50.64299816, -122.14351988));

Intent intent =
        new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
                    .setBoundsBias(latLngBounds)
                    .build(this); 

Autocomplete help-items for place search can be restricted to a particular country or a particular type of place by setting AutocompleteFilter object. AutocompleteFilter object can be created using AutocompleteFilter.Builder object. Country and type filter can be added to AutocompleteFilter.Builder object by calling setCountry and setTypeFilter methods. For more information on type filters and possible values, see AutoCompleteFilter.

By calling setFilter method on PlaceAutocomplete.IntentBuilder object, you can add AutocompleteFilter object as shown below.

 AutocompleteFilter.Builder filterBuilder = new AutocompleteFilter.Builder();
	filterBuilder.setCountry("US");
	filterBuilder.setTypeFilter(AutocompleteFilter.TYPE_FILTER_ADDRESS);

Intent intent =
	new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_OVERLAY)
		.setFilter(filterBuilder.build())
		.build(this); 

Autocomplete UI widget example in full screen mode

google places api autocomplete ui widget example

Autocomplete UI widget with auto complete help.

google places api autocomplete ui search widget example

Custom Auto Complete Places API Search

Now, let’s see how to create custom auto complete component. You may need custom auto component to satisfy requirements of your app. For example, providing filters such as country and place types in the UI so that user can set filters to restrict place search to a particular country or place types.

To get places data based on the search text, GeoDataClient API needs to be used. Method getAutocompletePredictions of GeoDataClient API takes search string and auto complete filter as input and returns AutocompletePredictionBufferResponse object which contains AutoCompletePrediction objects.

geoDataClient = Places.getGeoDataClient(this, null);
Task<AutocompletePredictionBufferResponse> results =
        geoDataClient.getAutocompletePredictions(query, null, null);

results.addOnCompleteListener(new OnCompleteListener<AutocompletePredictionBufferResponse>() {
    @Override
    public void onComplete(@NonNull Task<AutocompletePredictionBufferResponse> task) {
        if(task.isSuccessful()){
            AutocompletePredictionBufferResponse predictions = task.getResult();
            Log.d(TAG, "Auto complete predictions size "+predictions.getCount());
        }else{
            Log.d(TAG, "Auto complete prediction unsuccessful");
        }
    }
});

The below screen shows our example custom auto complete screen with country and place type filters. It allows user to enter two letters country code and to select place type from spinner. These values can be passed to the API call to get restricted place predictions.

android google places api custom auto complete search country filter example

Place type spinner contains possible values so that user can select and restrict place predictions to the selected place type.

android google places api custom auto complete search place type filter example

User can enter search text into AutoCompleteTextView. As user types into the field, getAutocompletePredictions api call is made to get predictions and displayed in search auto complete dropdown.

AutoCompleteTextView uses adapter and filter to perform search using user entered text and to display results in dropdown.

For more information on AutoCompleteTextView, please see AutoCompleteTextView dynamic search example

android google places api custom auto complete search example

Activity

User selected place from auto complete list can be captured in OnItemClickListener attached to AutoCompleteTextView. You can implement your app specific feature with the selected place, like for example adding notes to a place.

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Spinner;
import android.widget.Toast;

import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBufferResponse;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

import java.util.ArrayList;
import java.util.List;

public class AutoCompleteActivity extends AppCompatActivity {

    public static final String TAG = "AutoCompleteActivity";
    private static final int AUTO_COMP_REQ_CODE = 2;

    protected GeoDataClient geoDataClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.custom_auto_complete);

        Toolbar tb = findViewById(R.id.toolbar);
        setSupportActionBar(tb);
        tb.setSubtitle("Auto Complete");

        //set place types spinner data from array
        Spinner placeType = findViewById(R.id.place_type);
        ArrayAdapter<CharSequence> spinnerAdapter =
                ArrayAdapter.createFromResource(this,
                R.array.placeTypes, android.R.layout.simple_spinner_item);
        spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        placeType.setAdapter(spinnerAdapter);

        //Set adapter for autocomplete text view
        AutoCompleteTextView searchPlace = findViewById(R.id.search_place);

        CustomAutoCompleteAdapter adapter =  new CustomAutoCompleteAdapter(this);
        searchPlace.setAdapter(adapter);
        searchPlace.setOnItemClickListener(onItemClickListener);

    }
    private AdapterView.OnItemClickListener onItemClickListener =
            new AdapterView.OnItemClickListener(){
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

                    Toast.makeText(AutoCompleteActivity.this,
                            "selected place "
                                    + ((zoftino.com.places.Place)adapterView.
                                    getItemAtPosition(i)).getPlaceText()
                            , Toast.LENGTH_SHORT).show();
                    //do something with the selection
                    searchScreen();
                }
            };

    public void searchScreen(){
        Intent i = new Intent();
        i.setClass(this, AutoCompleteActivity.class);
        startActivity(i);
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".AutoCompleteActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary" />
    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp">
        <android.support.design.widget.TextInputLayout
            android:id="@+id/country_l"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">
            <android.support.design.widget.TextInputEditText
                android:id="@+id/country"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Country"/>
        </android.support.design.widget.TextInputLayout>
        <Spinner
            android:id="@+id/place_type"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/country_l"></Spinner>
        <AutoCompleteTextView
            android:id="@+id/search_place"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:hint="Search Location"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/place_type">
        </AutoCompleteTextView>
    </android.support.constraint.ConstraintLayout>
</LinearLayout>

Custom ArrayAdapter for AutoCompleteTextView

Every time search is performed, method performFiltering of Filter class defined in the adapter is called. This method is called in a worker thread. This is where getAutocompletePredictions api call is made to get predictions. Since the method performFiltering needs to return FilterResults and getAutocompletePredictions API runs asynchronously, performFiltering thread needs to wait for the results from API call. This is done using thread wait and notify.

 import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Spinner;
import android.widget.TextView;

import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBufferResponse;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

import java.util.ArrayList;
import java.util.List;

public class CustomAutoCompleteAdapter extends ArrayAdapter {
    public static final String TAG = "CustomAutoCompAdapter";
    private List<Place> dataList;
    private Context mContext;
    private GeoDataClient geoDataClient;

    private CustomAutoCompleteAdapter.CustomAutoCompleteFilter listFilter =
            new CustomAutoCompleteAdapter.CustomAutoCompleteFilter();

    private TextView country;
    private Spinner placeType;
    private int[] placeTypeValues;

    public CustomAutoCompleteAdapter(Context context) {
        super(context, android.R.layout.simple_dropdown_item_1line,
                new ArrayList<Place>());
        mContext = context;
        
        //get GeoDataClient
        geoDataClient = Places.getGeoDataClient(mContext, null);

        //get country textview, placetype spinner to get 
        // current values to perform research
        country = ((Activity) context).findViewById(R.id.country);
        placeType = ((Activity) context).findViewById(R.id.place_type);
        
        //spinner value map from array resources
        placeTypeValues = ((Activity) context).getResources().
                getIntArray(R.array.placeTypesValue);

    }

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

    @Override
    public Place getItem(int position) {
        return dataList.get(position);
    }

    @Override
    public View getView(int position, View view, @NonNull ViewGroup parent) {

        if (view == null) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(android.R.layout.simple_dropdown_item_1line,
                            parent, false);
        }

        TextView textOne = view.findViewById(android.R.id.text1);
        textOne.setText(dataList.get(position).getPlaceText());

        return view;
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return listFilter;
    }

    public class CustomAutoCompleteFilter extends Filter {
        private Object lock = new Object();
        private Object lockTwo = new Object();
        private boolean placeResults = false;


        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            FilterResults results = new FilterResults();
            placeResults = false;
            final List<Place> placesList = new ArrayList<>();

            if (prefix == null || prefix.length() == 0) {
                synchronized (lock) {
                    results.values = new ArrayList<Place>();
                    results.count = 0;
                }
            } else {
                final String searchStrLowerCase = prefix.toString().toLowerCase();

                Task<AutocompletePredictionBufferResponse> task
                        = getAutoCompletePlaces(searchStrLowerCase);

                task.addOnCompleteListener(new OnCompleteListener<AutocompletePredictionBufferResponse>() {
                    @Override
                    public void onComplete(@NonNull Task<AutocompletePredictionBufferResponse> task) {
                        if (task.isSuccessful()) {
                            Log.d(TAG, "Auto complete prediction successful");
                            AutocompletePredictionBufferResponse predictions = task.getResult();
                            Place autoPlace;
                            for (AutocompletePrediction prediction : predictions) {
                                autoPlace = new Place();
                                autoPlace.setPlaceId(prediction.getPlaceId());
                                autoPlace.setPlaceText(prediction.getFullText(null).toString());
                                placesList.add(autoPlace);
                            }
                            predictions.release();
                            Log.d(TAG, "Auto complete predictions size " + placesList.size());
                        } else {
                            Log.d(TAG, "Auto complete prediction unsuccessful");
                        }
                        //inform waiting thread about api call completion
                        placeResults = true;
                        synchronized (lockTwo) {
                            lockTwo.notifyAll();
                        }
                    }
                });

                //wait for the results from asynchronous API call
                while (!placeResults) {
                    synchronized (lockTwo) {
                        try {
                            lockTwo.wait();
                        } catch (InterruptedException e) {

                        }
                    }
                }
                results.values = placesList;
                results.count = placesList.size();
                Log.d(TAG, "Autocomplete predictions size after wait" + results.count);
            }

            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results.values != null) {
                dataList = (ArrayList<Place>) results.values;
            } else {
                dataList = null;
            }
            if (results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }

        private Task<AutocompletePredictionBufferResponse> getAutoCompletePlaces(String query) {
            //create autocomplete filter using data from filter Views
            AutocompleteFilter.Builder filterBuilder = new AutocompleteFilter.Builder();
            filterBuilder.setCountry(country.getText().toString());
            filterBuilder.setTypeFilter(placeTypeValues[placeType.getSelectedItemPosition()]);

            Task<AutocompletePredictionBufferResponse> results =
                    geoDataClient.getAutocompletePredictions(query, null,
                            filterBuilder.build());
            return results;
        }
    }
}
 

Array Xml for Populating PlaceTypes in Spinner

 <?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="placeTypes">
        <item>PlaceType</item>
        <item>Address</item>
        <item>Cities</item>
        <item>Establishment</item>
        <item>Geocode</item>
        <item>Regions</item>
        <item>All</item>
    </array>
    <array name="placeTypesValue">
        <item>0</item>
        <item>2</item>
        <item>5</item>
        <item>34</item>
        <item>1007</item>
        <item>4</item>
        <item>0</item>
    </array>
</resources>
 

Place

 public class Place {
    private String placeId;
    private String placeText;

    public String getPlaceId() {
        return placeId;
    }

    public void setPlaceId(String placeId) {
        this.placeId = placeId;
    }

    public String getPlaceText() {
        return placeText;
    }

    public void setPlaceText(String placeText) {
        this.placeText = placeText;
    }
    public String toString(){
        return placeText;
    }
}
 

It is required that certain attributes need to be displayed on the screen which shows places data to comply with policies and terms of using Google places API. For more information, see Google places API attributions.