Hi, in this blog we will learn how we can implement the auto place suggestion feature in our android application and make our application more efficient.
I will show you how we can achieve something like in the working of the Google maps search bar.
See the image below
Implementing something like this is super easy, all you need to do is follow this article.
Things you need to get started :
- Android Studio
- Google Developer Account
- Firebase Account
Before you start coding :
- Get your map and places api key from google developer console by enabling places and maps Sdk for android and google places and maps api. A total of 4 apis namely :
1) Places SDK for Android
2) Maps SDK for Android
3) Places API
4) Maps Static APISave the key obtained from here in your Android Manifet.xml file
1234567891011121314151617181920<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.webkul.mobikul.mapdemoapplication"><application//..>//..<!-- Goolge MAP API Key --><meta-dataandroid:name="com.google.android.maps.v2.API_KEY"android:value="REPLACE_WITH_YOUR_MAPS_API_KEY" /><meta-dataandroid:name="com.google.android.geo.API_KEY"android:value="REPLACE_WITH_YOUR_PLACES_API_KEY" />// NOTE : A Single key can also be used here//..</application></manifest>
- Register a project on Firebase. Download the google-services.json file from there and add to your Android project.
- Open build.gradle of your root android project and check these lines, Add if needed
1234567891011121314// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {//...dependencies {classpath 'com.android.tools.build:gradle:3.1.1'classpath 'com.google.gms:google-services:3.2.0'// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}//....} - Open build.gradle of your module and add these dependencies
123456789dependencies {//...// Google Mapsapi "com.google.android.gms:play-services-maps:15.0.1"api 'com.google.maps:google-maps-services:0.1.20'api 'com.google.maps.android:android-maps-utils:0.5'api 'com.google.android.gms:play-services-places:15.0.1'//...}
After these initial steps, you are good to start writing code.
APPROACH :
- We are using the AutoCompleteTextView to get what user types and will display the suggestions accordingly.
- We need to create the adapter for the AutoCompleteTextView
- Finally, we will display the result selected by the user.
CODE :
activity_main.xml file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context=".MainActivity"> <AutoCompleteTextView android:id="@+id/location_actv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:hint="@string/search_your_location" android:gravity="center_vertical" android:padding="8dp" android:layout_margin="16dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentBottom="true" android:layout_margin="16dp" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="The result will be displayed here" /> <TextView android:id="@+id/address_tv" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/location_display_tv" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </RelativeLayout> |
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
public class MainActivity extends AppCompatActivity { private AutoCompleteTextView locationSearchActv;// instance of AutoCompleteText View private TextView addressTv, locationDataTv; // TextViews Used to display the adddress selected by the user private GeoDataClient mGeoDataClient; private PlaceAutocompleteAdapter mPlaceAutocompleteAdapter; private static final LatLngBounds LAT_LNG_BOUNDS_DEFAULT = new LatLngBounds( new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362)); // The suggestion within this geometrical boundary will be displayed on top. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); locationSearchActv = (AutoCompleteTextView)findViewById(R.id.location_actv); addressTv = (TextView) findViewById(R.id.address_tv); locationDataTv = (TextView)findViewById(R.id.location_display_tv); mGeoDataClient = Places.getGeoDataClient(this); mPlaceAutocompleteAdapter = new PlaceAutocompleteAdapter(this,mGeoDataClient, LAT_LNG_BOUNDS_DEFAULT,null); locationSearchActv.setAdapter(mPlaceAutocompleteAdapter); locationSearchActv.setOnItemClickListener(mAutocompleteClickListener); } private AdapterView.OnItemClickListener mAutocompleteClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { /* Retrieve the place ID of the selected item from the Adapter. The adapter stores each Place suggestion in a AutocompletePrediction from which we read the place ID and title. */ final AutocompletePrediction item = mPlaceAutocompleteAdapter.getItem(position); final String placeId = item.getPlaceId(); /* Issue a request to the Places Geo Data Client to retrieve a Place object with additional details about the place. */ Task<PlaceBufferResponse> placeResult = mGeoDataClient.getPlaceById(placeId); placeResult.addOnCompleteListener(mUpdatePlaceDetailsCallback); } }; private OnCompleteListener<PlaceBufferResponse> mUpdatePlaceDetailsCallback = new OnCompleteListener<PlaceBufferResponse>() { @Override public void onComplete(Task<PlaceBufferResponse> task) { try { PlaceBufferResponse places = task.getResult(); // Get the Place object from the buffer. final Place place = places.get(0); addressTv.setText(place.getAddress().toString()); locationDataTv.setText("Latitude : "+String.valueOf(place.getLatLng().latitude)+"\n Longitude : "+String.valueOf(place.getLatLng().longitude)); places.release(); } catch (RuntimeRemoteException e) { // Request did not complete successfully return; } } }; } |
PlaceAutoCompleteAdapter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
/* * Copyright (C) 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.webkul.mobikul.mapdemoapplication; 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.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.data.DataBufferUtils; 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.maps.model.LatLngBounds; import com.google.android.gms.tasks.RuntimeExecutionException; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Adapter that handles Autocomplete requests from the Places Geo Data Client. * {@link AutocompletePrediction} results from the API are frozen and stored directly in this * adapter. (See {@link AutocompletePrediction#freeze()}.) */ public class PlaceAutocompleteAdapter extends ArrayAdapter<AutocompletePrediction> implements Filterable { private static final String TAG = "PlaceAutocompleteAdapt"; private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD); /** * Current results returned by this adapter. */ private ArrayList<AutocompletePrediction> mResultList; /** * Handles autocomplete requests. */ private GeoDataClient mGeoDataClient; /** * 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 ArrayAdapter#ArrayAdapter(Context, int) */ public PlaceAutocompleteAdapter(Context context, GeoDataClient geoDataClient, LatLngBounds bounds, AutocompleteFilter filter) { super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1); mGeoDataClient = geoDataClient; 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. * 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 GeoDataClient#getAutocompletePredictions(String, LatLngBounds, AutocompleteFilter) * @see AutocompletePrediction#freeze() */ private ArrayList<AutocompletePrediction> getAutocomplete(CharSequence constraint) { 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. Task<AutocompletePredictionBufferResponse> results = mGeoDataClient.getAutocompletePredictions(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. try { Tasks.await(results, 60, TimeUnit.SECONDS); } catch (ExecutionException | InterruptedException | TimeoutException e) { e.printStackTrace(); } try { AutocompletePredictionBufferResponse autocompletePredictions = results.getResult(); Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount() + " predictions."); // Freeze the results immutable representation that can be stored safely. return DataBufferUtils.freezeAndClose(autocompletePredictions); } catch (RuntimeExecutionException e) { // If the query did not complete successfully return null Toast.makeText(getContext(), "Error contacting API: " + e.toString(), Toast.LENGTH_SHORT).show(); Log.e(TAG, "Error getting autocomplete prediction API call", e); return null; } } } |
And With this, you will now be able to suggest places within your application.
For better understanding try creating a small demo project first using these files only.
Keep coding and Keep Sharing 🙂