How to implement google places autocomplete programmatically

34,612

Solution 1

New Version 2019

build.gradle (Module:app)

implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.gms:play-services-places:17.0.0'
implementation 'com.google.android.libraries.places:places:1.1.0'

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:orientation="vertical"
    android:padding="15dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MapsActivity">
    <EditText
        android:id="@+id/place_search"
        android:hint="Search"
        android:inputType="textPostalAddress"
        android:padding="15dp"
        android:layout_marginBottom="15dp"
        android:drawableLeft="@drawable/places_ic_search"
        android:background="@drawable/edit_text_border"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/places_recycler_view"
        android:background="#FFF"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </androidx.recyclerview.widget.RecyclerView>
</LinearLayout>

Activity

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.zaaibo.drive.R;
import com.zaaibo.drive.adapter.PlacesAutoCompleteAdapter;

public class MapsActivity extends AppCompatActivity implements PlacesAutoCompleteAdapter.ClickListener{

    private PlacesAutoCompleteAdapter mAutoCompleteAdapter;
    private RecyclerView recyclerView;

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

        Places.initialize(this, getResources().getString(R.string.google_maps_key));

        recyclerView = (RecyclerView) findViewById(R.id.places_recycler_view);
        ((EditText) findViewById(R.id.place_search)).addTextChangedListener(filterTextWatcher);

        mAutoCompleteAdapter = new PlacesAutoCompleteAdapter(this);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAutoCompleteAdapter.setClickListener(this);
        recyclerView.setAdapter(mAutoCompleteAdapter);
        mAutoCompleteAdapter.notifyDataSetChanged();
    }

    private TextWatcher filterTextWatcher = new TextWatcher() {
        public void afterTextChanged(Editable s) {
            if (!s.toString().equals("")) {
                mAutoCompleteAdapter.getFilter().filter(s.toString());
                if (recyclerView.getVisibility() == View.GONE) {recyclerView.setVisibility(View.VISIBLE);}
            } else {
                if (recyclerView.getVisibility() == View.VISIBLE) {recyclerView.setVisibility(View.GONE);}
            }
        }
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
        public void onTextChanged(CharSequence s, int start, int before, int count) { }
    };

    @Override
    public void click(Place place) {
        Toast.makeText(this, place.getAddress()+", "+place.getLatLng().latitude+place.getLatLng().longitude, Toast.LENGTH_SHORT).show();
    }
}

Adapter

import android.content.Context;
import android.graphics.Typeface;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.android.libraries.places.api.model.AutocompletePrediction;
import com.google.android.libraries.places.api.model.AutocompleteSessionToken;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.net.FetchPlaceRequest;
import com.google.android.libraries.places.api.net.FetchPlaceResponse;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest;
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse;
import com.google.android.libraries.places.api.net.PlacesClient;
import com.zaaibo.drive.R;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class PlacesAutoCompleteAdapter extends RecyclerView.Adapter<PlacesAutoCompleteAdapter.PredictionHolder> implements Filterable {
    private static final String TAG = "PlacesAutoAdapter";
    private ArrayList<PlaceAutocomplete> mResultList = new ArrayList<>();

    private Context mContext;
    private CharacterStyle STYLE_BOLD;
    private CharacterStyle STYLE_NORMAL;
    private final PlacesClient placesClient;
    private ClickListener clickListener;

    public PlacesAutoCompleteAdapter(Context context) {
        mContext = context;
        STYLE_BOLD = new StyleSpan(Typeface.BOLD);
        STYLE_NORMAL = new StyleSpan(Typeface.NORMAL);
        placesClient = com.google.android.libraries.places.api.Places.createClient(context);
    }

    public void setClickListener(ClickListener clickListener) {
        this.clickListener = clickListener;
    }

    public interface ClickListener {
        void click(Place place);
    }

    /**
     * Returns the filter for the current set of autocomplete results.
     */
    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults results = new FilterResults();
                // Skip the autocomplete query if no constraints are given.
                if (constraint != null) {
                    // Query the autocomplete API for the (constraint) search string.
                    mResultList = getPredictions(constraint);
                    if (mResultList != null) {
                        // The API successfully returned 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();
                }
            }
        };
    }


    private ArrayList<PlaceAutocomplete> getPredictions(CharSequence constraint) {

        final ArrayList<PlaceAutocomplete> resultList = new ArrayList<>();

        // 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();

        //https://gist.github.com/graydon/11198540
        // Use the builder to create a FindAutocompletePredictionsRequest.
        FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
                // Call either setLocationBias() OR setLocationRestriction().
                //.setLocationBias(bounds)
                //.setCountry("BD")
                //.setTypeFilter(TypeFilter.ADDRESS)
                .setSessionToken(token)
                .setQuery(constraint.toString())
                .build();

        Task<FindAutocompletePredictionsResponse> autocompletePredictions = placesClient.findAutocompletePredictions(request);

        // This method should have been called off the main UI thread. Block and wait for at most
        // 60s for a result from the API.
        try {
            Tasks.await(autocompletePredictions, 60, TimeUnit.SECONDS);
        } catch (ExecutionException | InterruptedException | TimeoutException e) {
            e.printStackTrace();
        }

        if (autocompletePredictions.isSuccessful()) {
            FindAutocompletePredictionsResponse findAutocompletePredictionsResponse = autocompletePredictions.getResult();
            if (findAutocompletePredictionsResponse != null)
                for (AutocompletePrediction prediction : findAutocompletePredictionsResponse.getAutocompletePredictions()) {
                    Log.i(TAG, prediction.getPlaceId());
                    resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getPrimaryText(STYLE_NORMAL).toString(), prediction.getFullText(STYLE_BOLD).toString()));
                }

            return resultList;
        } else {
            return resultList;
        }

    }

    @NonNull
    @Override
    public PredictionHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View convertView = layoutInflater.inflate(R.layout.place_recycler_item_layout, viewGroup, false);
        return new PredictionHolder(convertView);
    }

    @Override
    public void onBindViewHolder(@NonNull PredictionHolder mPredictionHolder, final int i) {
        mPredictionHolder.address.setText(mResultList.get(i).address);
        mPredictionHolder.area.setText(mResultList.get(i).area);
    }

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

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

    public class PredictionHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private TextView address, area;
        private LinearLayout mRow;

        PredictionHolder(View itemView) {
            super(itemView);
            area = itemView.findViewById(R.id.place_area);
            address = itemView.findViewById(R.id.place_address);
            mRow = itemView.findViewById(R.id.place_item_view);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            PlaceAutocomplete item = mResultList.get(getAdapterPosition());
            if (v.getId() == R.id.place_item_view) {

                String placeId = String.valueOf(item.placeId);

                List<Place.Field> placeFields = Arrays.asList(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS);
                FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields).build();
                placesClient.fetchPlace(request).addOnSuccessListener(new OnSuccessListener<FetchPlaceResponse>() {
                    @Override
                    public void onSuccess(FetchPlaceResponse response) {
                        Place place = response.getPlace();
                        clickListener.click(place);
                    }
                }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception exception) {
                        if (exception instanceof ApiException) {
                            Toast.makeText(mContext, exception.getMessage() + "", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
            }
        }
    }

    /**
     * Holder for Places Geo Data Autocomplete API results.
     */
    public class PlaceAutocomplete {

        public CharSequence placeId;
        public CharSequence address, area;

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

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

Solution 2

public class PlaceArrayAdapter extends ArrayAdapter<PlaceArrayAdapter.PlaceAutocomplete> implements Filterable {

    private static final String TAG = "PlaceArrayAdapter";
    private final PlacesClient placesClient;
    private RectangularBounds mBounds;
    private ArrayList<PlaceAutocomplete> mResultList = new ArrayList<>();
    public Context context;

    /**
     * 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, RectangularBounds bounds) {
        super(context, resource);
        this.context = context;
        mBounds = bounds;
        placesClient = com.google.android.libraries.places.api.Places.createClient(context);
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        View view = super.getView(position, convertView, parent);
        TypefaceHelper.typeface(view);
        return view;
    }

    @Override
    public int getCount() {
        if (mResultList == null)
            return 0;
        else
            return mResultList.size();
    }

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

    private ArrayList<PlaceAutocomplete> getPredictions(CharSequence constraint) {
          
            final ArrayList<PlaceAutocomplete> resultList = new ArrayList<>();
    
            // 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();
    
    
            // Use the builder to create a FindAutocompletePredictionsRequest.
            FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
                    // Call either setLocationBias() OR setLocationRestriction().
                    // .setLocationBias(bounds)
                    .setLocationBias(mBounds)
                    //.setCountry("au")
                    //   .setTypeFilter(TypeFilter.ADDRESS)
                    .setSessionToken(token)
                    .setQuery(constraint.toString())
                    .build();
    
            Task<FindAutocompletePredictionsResponse> autocompletePredictions = placesClient.findAutocompletePredictions(request);
    
            // This method should have been called off the main UI thread. Block and wait for at most
            // 60s for a result from the API.
            try {
                Tasks.await(autocompletePredictions, 60, TimeUnit.SECONDS);
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                e.printStackTrace();
            }
    
            if (autocompletePredictions.isSuccessful()) {
                FindAutocompletePredictionsResponse findAutocompletePredictionsResponse = autocompletePredictions.getResult();
                if (findAutocompletePredictionsResponse != null)
                    for (com.google.android.libraries.places.api.model.AutocompletePrediction prediction : findAutocompletePredictionsResponse.getAutocompletePredictions()) {
                        Log.i(TAG, prediction.getPlaceId());
                        Log.i(TAG, prediction.getPrimaryText(null).toString());
    
                        resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getFullText(null).toString()));
    
                    }
    
                return resultList;
            } else {
                return resultList;
            }
    
        }
    
        @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
                        Log.d(TAG, "Before Prediction");
                        mResultList = getPredictions(constraint);
                        Log.d(TAG, "After Prediction");
                        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;
        }
    
    public 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();
        }
    }
}
/* Now call adapter from activity */

autoCompleteTextView = findViewById(R.id.autoCompleteTextView);
autoCompleteTextView.setThreshold(3);

//autoCompleteTextView.setOnItemClickListener(mAutocompleteClickListener);

mPlaceArrayAdapter = new PlaceArrayAdapter(
    this, R.layout.adapter_place_array, CURRENT_LOCATION_BONDS
);

Solution 3

In Kotlin

TypeFilter.CITIES

enter image description here>

TypeFilter.ADDRESS

enter image description here

  1. activity layout file

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 
        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="wrap_content"
        android:orientation="vertical">
    
    <fragment
        android:id="@+id/mapLocation"
        android:name="com.google.android.gms.maps.MapFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="@dimen/_2sdp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/autoCompleteEditText"/>
    
    <androidx.appcompat.widget.AppCompatAutoCompleteTextView
        android:id="@+id/autoCompleteEditText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="0.9"
        android:gravity="start"
        android:hint="Enter Location or Drag map to select location"
        android:padding="@dimen/_10sdp"
        android:singleLine="true"
        android:textColor="@color/color_black_two"
        android:textColorHint="@color/color_brown_grey"
        android:textSize="@dimen/_13ssp"
        app:layout_constraintStart_toStartOf="@+id/mapLocation"
        app:layout_constraintEnd_toEndOf="@+id/mapLocation"
        app:layout_constraintBottom_toBottomOf="parent"/>    
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. In Activity

    class MainActivity :AppCompatActivity(), OnMapReadyCallback {
    
    private var mMap: GoogleMap? = null
    private var placeAdapter: PlaceArrayAdapter? = null
    private lateinit var mPlacesClient: PlacesClient
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    
    override fun onPostCreate(savedInstanceState: Bundle?) {
        super.onPostCreate(savedInstanceState)
    
        Places.initialize(this, "GOOGLE_MAPS_KEY")
        mPlacesClient = Places.createClient(this)
    
        val mapFragment: SupportMapFragment? = supportFragmentManager.findFragmentById(R.id.mapLocation) as? SupportMapFragment
        mapFragment?.getMapAsync(this)
    
        placeAdapter = PlaceArrayAdapter(this, R.layout.layout_item_places, mPlacesClient)
        autoCompleteEditText.setAdapter(placeAdapter)
    
        autoCompleteEditText.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
            val place = parent.getItemAtPosition(position) as PlaceDataModel
            autoCompleteEditText.apply {
                setText(place.fullText)
                setSelection(autoCompleteEditText.length())
            }
        }
    }
    
    override fun onMapReady(p0: GoogleMap?) {
    
    }
    }
    
  • PlaceArrayAdapter

     class PlaceArrayAdapter(context: Context,val resource: Int, val mPlacesClient: PlacesClient) : ArrayAdapter<PlaceDataModel>(context, resource), Filterable {
    
     private var mContext : Context = context
     private var resultList = arrayListOf<PlaceDataModel>()
    
     override fun getCount(): Int {
         return when {
             resultList.isNullOrEmpty() -> 0
             else -> resultList.size
         }
     }
    
     override fun getItem(position: Int): PlaceDataModel? {
         return when {
             resultList.isNullOrEmpty() -> null
             else -> resultList[position]
         }
     }
    
     override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
         var view = convertView
         val viewHolder: ViewHolder
         if (view == null) {
             viewHolder = ViewHolder()
             view = LayoutInflater.from(context).inflate(resource, parent, false)
             viewHolder.description = view.findViewById(R.id.searchFullText) as TextView
             view.tag = viewHolder
         } else {
             viewHolder = view.tag as ViewHolder
         }
         bindView(viewHolder, resultList, position)
         return view!!
     }
    
     private fun bindView(viewHolder: ViewHolder, place: ArrayList<PlaceDataModel>, position: Int) {
         if (!place.isNullOrEmpty()) {
             viewHolder.description?.text = place[position].fullText
         }
     }
    
     override fun getFilter(): Filter {
         return object : Filter() {
             override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                 if (results != null && results.count > 0) {
                     mContext.runOnUiThread {
                         notifyDataSetChanged()
                     }
                 } else {
                     notifyDataSetInvalidated()
                 }
             }
    
             override fun performFiltering(constraint: CharSequence?): FilterResults {
                 val filterResults = FilterResults()
                 if (constraint != null) {
                     resultList.clear()
                     val address = getAutocomplete(mPlacesClient, constraint.toString())
                     address?.let {
                         for (i in address.indices) {
                             val item = address[i]
                             resultList.add(PlaceDataModel(item.placeId, item.getFullText(StyleSpan(Typeface.BOLD)).toString()))
                         }
                     }
                     filterResults.values = resultList
                     filterResults.count = resultList.size
                 }
                 return filterResults
             }
         }
     }
    
     internal class ViewHolder {
         var description: TextView? = null
     }
     }
    
  • getAutocomplete method (you can add in adapter or extention)

     fun getAutocomplete(mPlacesClient: PlacesClient, constraint: CharSequence): List<AutocompletePrediction> {
       var list = listOf<AutocompletePrediction>()
       val token = AutocompleteSessionToken.newInstance()
       val request = FindAutocompletePredictionsRequest.builder().setTypeFilter(TypeFilter.ADDRESS).setSessionToken(token).setQuery(constraint.toString()).build()
       val prediction = mPlacesClient.findAutocompletePredictions(request)
       try {
           Tasks.await(prediction, TASK_AWAIT, TimeUnit.SECONDS)
       } catch (e: ExecutionException) {
           e.printStackTrace()
       } catch (e: InterruptedException) {
           e.printStackTrace()
       } catch (e: TimeoutException) {
           e.printStackTrace()
       }
    
       if (prediction.isSuccessful) {
           val findAutocompletePredictionsResponse = prediction.result
         findAutocompletePredictionsResponse?.let {
               list = findAutocompletePredictionsResponse.autocompletePredictions
           }
           return list
       }
       return list
     }
    
  • R.layout.layout_item_places

     <androidx.constraintlayout.widget.ConstraintLayout 
         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="wrap_content"
         android:orientation="vertical">
    
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/searchFullText"
         style="@style/TextViewGraphikRegular"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/_5sdp"
         android:layout_marginEnd="@dimen/_8sdp"
         android:ellipsize="end"
         android:gravity="center_vertical"
         android:maxLines="2"
         android:padding="@dimen/_8sdp"
         android:text="@{dataModel.fullText}"
         android:textColor="@android:color/black"
         android:textSize="@dimen/_10ssp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         tools:text="Ahmedabad" />
     </androidx.constraintlayout.widget.ConstraintLayout>
    

Change this filter :

  • open MapExtention File
  • getAutocomplete Method
  • setTypeFilter(TypeFilter.CITIES)

#Github demo

Solution 4

I used this adapter class after enabling Google Places API:-

public class PlaceAutoCompleteAdapter
        extends ArrayAdapter<AutocompletePrediction> implements Filterable {

    private static final String TAG = "PlaceAutoCompleteAd";
    private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
    /**
     * Current results returned by this adapter.
     */
    private ArrayList<AutocompletePrediction> mResultList;

    /**
     * Handles autocomplete requests.
     */
    private GoogleApiClient mGoogleApiClient;

    /**
     * The bounds used for Places Geo Data autocomplete API requests.
     */
    private LatLngBounds mBounds;

    /**
     * The autocomplete filter used to restrict queries to a specific set of place types.
     */
    private AutocompleteFilter mPlaceFilter;

    /**
     * Initializes with a resource for text rows and autocomplete query bounds.
     *
     * @see android.widget.ArrayAdapter#ArrayAdapter(android.content.Context, int)
     */
    public PlaceAutoCompleteAdapter(Context context, GoogleApiClient googleApiClient,
                                    LatLngBounds bounds, AutocompleteFilter filter) {
        super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1);
        mGoogleApiClient = googleApiClient;
        mBounds = bounds;
        mPlaceFilter = filter;
    }

    /**
     * Sets the bounds for all subsequent queries.
     */
    public void setBounds(LatLngBounds bounds) {
        mBounds = bounds;
    }

    /**
     * Returns the number of results received in the last autocomplete query.
     */
    @Override
    public int getCount() {
        return mResultList.size();
    }

    /**
     * Returns an item from the last autocomplete query.
     */
    @Override
    public AutocompletePrediction getItem(int position) {
        return mResultList.get(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = super.getView(position, convertView, parent);

        // Sets the primary and secondary text for a row.
        // Note that getPrimaryText() and getSecondaryText() return a CharSequence that may contain
        // styling based on the given CharacterStyle.

        AutocompletePrediction item = getItem(position);

        TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
        TextView textView2 = (TextView) row.findViewById(android.R.id.text2);
        textView1.setText(item.getPrimaryText(STYLE_BOLD));
        textView2.setText(item.getSecondaryText(STYLE_BOLD));

        return row;
    }

    /**
     * Returns the filter for the current set of autocomplete results.
     */
    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults results = new FilterResults();

                // We need a separate list to store the results, since
                // this is run asynchronously.
                ArrayList<AutocompletePrediction> filterData = new ArrayList<>();

                // Skip the autocomplete query if no constraints are given.
                if (constraint != null) {
                    // Query the autocomplete API for the (constraint) search string.
                    filterData = getAutocomplete(constraint);
                }

                results.values = filterData;
                if (filterData != null) {
                    results.count = filterData.size();
                } else {
                    results.count = 0;
                }

                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.
                    mResultList = (ArrayList<AutocompletePrediction>) results.values;
                    notifyDataSetChanged();
                } else {
                    // The API did not return any results, invalidate the data set.
                    notifyDataSetInvalidated();
                }
            }

            @Override
            public CharSequence convertResultToString(Object resultValue) {
                // Override this method to display a readable result in the AutocompleteTextView
                // when clicked.
                if (resultValue instanceof AutocompletePrediction) {
                    return ((AutocompletePrediction) resultValue).getFullText(null);
                } else {
                    return super.convertResultToString(resultValue);
                }
            }
        };
    }

    /**
     * Submits an autocomplete query to the Places Geo Data Autocomplete API.
     * Results are returned as frozen AutocompletePrediction objects, ready to be cached.
     * objects to store the Place ID and description that the API returns.
     * Returns an empty list if no results were found.
     * Returns null if the API client is not available or the query did not complete
     * successfully.
     * This method MUST be called off the main UI thread, as it will block until data is returned
     * from the API, which may include a network request.
     *
     * @param constraint Autocomplete query string
     * @return Results from the autocomplete API or null if the query was not successful.
     * @see Places#GEO_DATA_API#getAutocomplete(CharSequence)
     * @see AutocompletePrediction#freeze()
     */
    private ArrayList<AutocompletePrediction> getAutocomplete(CharSequence constraint) {
        if (mGoogleApiClient.isConnected()) {
            Log.i(TAG, "Starting autocomplete query for: " + constraint);

            // Submit the query to the autocomplete API and retrieve a PendingResult that will
            // contain the results when the query completes.
            PendingResult<AutocompletePredictionBuffer> results =
                    Places.GeoDataApi
                            .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                    mBounds, mPlaceFilter);

            // This method should have been called off the main UI thread. Block and wait for at most 60s
            // for a result from the API.
            AutocompletePredictionBuffer autocompletePredictions = results
                    .await(60, TimeUnit.SECONDS);

            // Confirm that the query completed successfully, otherwise return null
            final Status status = autocompletePredictions.getStatus();
            if (!status.isSuccess()) {
                Toast.makeText(getContext(), "Error contacting API: " + status.toString(),
                        Toast.LENGTH_SHORT).show();
                Log.e(TAG, "Error getting autocomplete prediction API call: " + status.toString());
                autocompletePredictions.release();
                return null;
            }

            Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
                    + " predictions.");

            // Freeze the results immutable representation that can be stored safely.
            return DataBufferUtils.freezeAndClose(autocompletePredictions);
        }
        Log.e(TAG, "Google API client is not connected for autocomplete query.");
        return null;
    }


}

I am also aware of this given video link

Solution 5

Updated Kotlin answer for 2021 with using recycler view custom layout

Layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_search_bar"
        style="@style/GreyEditTextStyle"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="20dp"
        android:maxLines="1"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_select_location"
        style="@style/PrimaryButtonStyleThin"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_marginEnd="20dp"
        android:elevation="7dp"
        android:text="@string/text_select_location"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/et_search_bar" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_place_results"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:paddingStart="30dp"
        app:layout_constraintEnd_toEndOf="@id/btn_select_location"
        app:layout_constraintStart_toStartOf="@id/et_search_bar"
        app:layout_constraintTop_toBottomOf="@id/btn_select_location" />

</androidx.constraintlayout.widget.ConstraintLayout>

RecyclerView Adapter

class PlacesResultAdapter(
    var mContext: Context,
    val onClick: (prediction: AutocompletePrediction) -> Unit,
) :
    RecyclerView.Adapter<PlacesResultAdapter.ViewHolder>(), Filterable {
    private var mResultList: ArrayList<AutocompletePrediction>? = arrayListOf()
    private val placesClient: PlacesClient = Places.createClient(mContext)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view =
            LayoutInflater.from(parent.context)
                .inflate(R.layout.item_place_suggestions, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return mResultList!!.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.onBind(position)
        holder.itemView.setOnClickListener {
            onClick(mResultList?.get(position)!!)
        }
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun onBind(position: Int) {
            val res = mResultList?.get(position)
            itemView.apply {
                tv_address_title.text = res?.getPrimaryText(null)
                tv_address_sub_title.text = res?.getSecondaryText(null)
            }
        }
    }

    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(constraint: CharSequence): FilterResults {
                val results = FilterResults()
                mResultList = getPredictions(constraint)
                if (mResultList != null) {
                    results.values = mResultList
                    results.count = mResultList!!.size
                }
                return results
            }

            override fun publishResults(constraint: CharSequence, results: FilterResults) {

            }
        }
    }

    private fun getPredictions(constraint: CharSequence): ArrayList<AutocompletePrediction>? {
        val result: ArrayList<AutocompletePrediction> = arrayListOf()
        val token = AutocompleteSessionToken.newInstance()
        val request =
            FindAutocompletePredictionsRequest.builder() // Call either setLocationBias() OR setLocationRestriction().
                //.setLocationBias(bounds)
                //.setCountries("SG","LK")
                //.setTypeFilter(TypeFilter.ADDRESS)
                .setSessionToken(token)
                .setQuery(constraint.toString())
                .build()

        placesClient.findAutocompletePredictions(request)
            .addOnSuccessListener { response: FindAutocompletePredictionsResponse ->
                result.addAll(response.autocompletePredictions)
                notifyDataSetChanged()
            }.addOnFailureListener { exception: Exception? ->
                if (exception is ApiException) {
                    Log.e("TAG", "Place not found: " + exception.statusCode)
                    mContext.showToastLong("Place not found")
                }
            }
        return result
    }
}

Activity code sample

private fun init() {
    val apiKey = getString(R.string.places_api_key)
    if (!Places.isInitialized()) {
        Places.initialize(this, apiKey)
    }
    val placesClient = Places.createClient(this)
    placeAdapter = PlacesResultAdapter(this) { prediction ->
        pickedLocation = prediction
        et_search_bar.setText(prediction.getFullText(null).toString())
        rv_place_results.gone()
    }
    rv_place_results.layoutManager = LinearLayoutManager(this)
    rv_place_results.adapter = placeAdapter

    et_search_bar.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }

        override fun afterTextChanged(text: Editable?) {
            if (text.toString().isNotEmpty()) {
                placeAdapter!!.filter.filter(text.toString())
                if (rv_place_results.isGone) {
                    rv_place_results.show()
                }
            } else {
                if (rv_place_results.isVisible) {
                    rv_place_results.gone()
                }
            }
        }
    })
}
Share:
34,612
Admin
Author by

Admin

Updated on July 31, 2022

Comments

  • Admin
    Admin over 1 year

    Autocomplete predictions don't show up in my MapsActivity when i type text in AutoCompleteTextView (mSearchText).

    I tried following the tutorial at the following link: https://www.youtube.com/watch?v=6Trdd9EnmqY&list=PLgCYzUzKIBE-vInwQhGSdnbyJ62nixHCt&index=8. Some methods used in it recently became deprecated, so I checked the documentation at https://developers.google.com/places/android-sdk/autocomplete, especially "Get place predictions programmatically" and "Migrating to the New Places SDK Client". I also followed a guide from the same guy to set up my API key, and I think that shouldn't be the problem here. This is my first Android application and my first question on this site, so please be patient with me.

    Places.initialize(getApplicationContext(), getString(R.string.google_maps_API_key));
    PlacesClient placesClient = Places.createClient(this);
    
    FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
        .setLocationBias(RECTANGULAR_BOUNDS)
        .setQuery(mSearchText.getText().toString())
        .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());
                }
            });
    

    The Autocomplete predictions should show up as the user types in the searchbar, but that doesn't happen. I don't get any of the logs from the listeners either.

  • Edgar Khimich
    Edgar Khimich almost 5 years
    The google made some changes: "Notice: 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. For details, see the migration guide."
  • user2234
    user2234 almost 5 years
    @EdgarKhimich: correct, but the above code does refer to the new Places sdk isn't it ?
  • Edgar Khimich
    Edgar Khimich almost 5 years
    @user12865: I think when I wrote that message - he had the old version.
  • Mustofa Kamal
    Mustofa Kamal almost 5 years
    @EdgarKhimich: Yes, Google Play Services 16.0.0) is deprecated. But this code is working perfectly and updated it from android documentation. developers.google.com/places/android-sdk/client-migration
  • Edgar Khimich
    Edgar Khimich almost 5 years
    I didn't say that your code is not working :) Just post as a reminder
  • Rat-a-tat-a-tat Ratatouille
    Rat-a-tat-a-tat Ratatouille almost 5 years
    @EdgarKhimich, does that mean, we would be billed even for autocomplete? And if we do not upgrade to the compatible library then would the sdk work for existing apps?
  • Edgar Khimich
    Edgar Khimich almost 5 years
    @Rat-a-tat-a-tatRatatouille I can't find the details for this update but about pricing here it is: An Autocomplete – Per Request SKU is charged for requests to the Places API Place Autocomplete service, or the Maps JavaScript API’s Places Autocomplete service, that do not include a session token. Calls made from an invalid Autocomplete session (for example, a session that reuses a session token) are also charged an Autocomplete – Per Request SKU. The more information you can find here: developers.google.com/maps/billing/… Hope it help
  • reverie_ss
    reverie_ss almost 5 years
    I had issues using PlaceAutocomplete, so I replaced it with AutocompletePrediction
  • Alex455
    Alex455 about 4 years
    @Vijesh Jat, How to use TypefaceHelper? Please make an example?
  • Vijesh Jat
    Vijesh Jat about 4 years
    @Alex455 In above code , TypeFaceHelper is my custom class for custom font support. This is not releated with place api
  • Shweta Chauhan
    Shweta Chauhan about 4 years
    still, code id not available at github but will do in short time. Btw, here I added all code which help you. let me know if you required more.
  • Shweta Chauhan
    Shweta Chauhan about 4 years
  • Shweta Chauhan
    Shweta Chauhan about 4 years
    @PriyankaKadam : Please add star in github and upvote here if it's useful to you.
  • Terranology
    Terranology over 3 years
    It generate a new AutocompleteSessionToken.newInstance() for each letter you write. I think we should use a new token for each session (from start tipping to select the place) to reduce billing
  • Arjun
    Arjun over 3 years
    Hi @Terranology could you please help me understand how to use a new token for each session to reduce billing? Thank You
  • DeveloperApps
    DeveloperApps about 3 years
    Does this work with session? In getPredictions, you generate a new Token after pressing each letter. Paying for every letter is not a good solution. Do you know a solution on how to make it work for search sessions?
  • Hardik Parmar
    Hardik Parmar over 2 years
    @MustofaKamal Very fantastic jobs. Highly appreciate
  • ekashking
    ekashking over 2 years
    In Adapter, declare AutocompleteSessionToken token; on the very top and then token = AutocompleteSessionToken.newInstance(); in public PlacesAutoCompleteAdapter(Context context) { ... } and then use token variable in .setSessionToken(token), cuz the way it's presented, it will produce a new token with every stroke of a keyboard. Also, don't forget to include token in FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields).setSessionToken(token).build();