{"id":2092,"date":"2015-02-06T23:04:03","date_gmt":"2015-02-06T17:34:03","guid":{"rendered":"http:\/\/codetheory.in\/?p=2092"},"modified":"2015-02-07T15:35:13","modified_gmt":"2015-02-07T10:05:13","slug":"google-place-api-autocomplete-service-in-android-application","status":"publish","type":"post","link":"https:\/\/codetheory.in\/google-place-api-autocomplete-service-in-android-application\/","title":{"rendered":"Google Place API Autocomplete Service in Android Application"},"content":{"rendered":"
Recently in one of my android applications I wanted to obtain the user’s location in terms of city and country that they can feed from their (edit) profile section. So one way to do this is basically have two dropdowns or dialogs (or even open an entirely new activity where the user can search through entities and select one). One of them would display all the countries in which, once a selection is made, the other one will show a restricted set of cities based on the prior country selection. Now the number of cities in the world is large, so to do this we’ll need to get a database that contains all the countries and cities and then make sure we can query that over HTTP to get the cities based on what the user types into the app (autocomplete box). We’ve to make sure the response is really quick and doesn’t cause lags. We can also bundle all the city and country data into our app but then that’ll blow up the apk size.<\/p>\n
<\/p>\n
Anyway, while searching the web and trying out apps like Airbnb<\/a> and Couchsurfing<\/a> I figured Google has the Place Autocomplete service<\/a> using which I can allow the user to type in a city name which’ll fetch all the cities containing that string from Google’s servers that can be presented in an AutoCompleteTextView<\/a>.<\/p>\n <\/p>\n So let’s see how to execute this.<\/p>\n The first thing to do is to get an API key from Google Developers Console. More on this here<\/a>. You’ll find four different key options – server, browser, android and iOS – you should go for the browser one as according to their documentation Places API doesn’t work with Android or iOS API key and since the app is like a browser client, a server key doesn’t make sense.<\/p>\n You should know about the usage limitation. All the information pertaining to that is available here<\/a>.<\/p>\n You should go through the Place Autocomplete service documentation<\/a> where you can learn everything about the requests and responses. Basically you’ll have to make a request to There are other optional parameters that you can read up on but the most important one that you’ll mostly use is the You can try a JavaScript based Place Autocomplete demo here<\/a>. In our case we only want cities, so here’s a sample request:<\/p>\n Here’s the type of response that you should expect:<\/p>\n You’ll note that the Adding Place Autocomplete to an Android app is fairly easy. All we have to do is issue search requests to the Place API over HTTP and parse the JSON\/XML response to display the results in an We’ll create a class called Next, make sure your layout file (for instance Also create another layout file ( Pretty cool! Now your Activity\/Fragment class must set the adapter for the AutoCompleteTextView:<\/p>\n It’s time to code the As the user types something inside the AutoCompleteTextView, the You should go through the AutoCompleteTextView source code<\/a> if you feel like. Also I’ve written an article<\/a> on the Filter class and Filterable interface before that you should consider reading to get a deep understanding of the filtering works in the piece of code shown above.<\/p>\n When an item from the AutoCompleteTextView’s list is selected, we can get the data associated with that list item, i.e., the selection made (from the ArrayList used as the data source by the adapter).<\/p>\n According to the Places API Policies<\/a>, if you’re using the Place Autocomplete service without Google Maps, then a powered by Google logo has to be shown. Generally this logo is shown right at the end of the AutoCompleteTextView list items. The logos for various platforms are available in the policies page<\/a> that you can download and use.<\/p>\n So how to show up the logo in the autocomplete list ? AutoCompleteTextView doesn’t have In practise, this is how a quick modification to our adapter implementation would look like:<\/p>\n This looks really dirty and doesn’t make use of cache. You should implement the ViewHolder pattern for a better efficient implementation. Also a small line of code will have to be added in the Notice the In the current situation, when the user types something in the box, our code will make API requests to Places Autocomplete service with every character typed (or removed). This can quickly eat up our usage limit. So to reduce the total number of requests made, we’ve to set a timeout for each request as something is typed into the input box. What this means is that when the user is done typing wait for a short time like 500ms or 1000ms (1s) and then make API requests.<\/p>\n So initially I was a little stuck on how to do this. I thought the filtering logic will have to be modified. but then that is sort of not possible. Internally, AutoCompleteTextView attaches a TextWatcher to itself whose Instead what I did was empty the filtering logic and add my own TextWatcher to achieve all of this. The TextWatcher would post the autocomplete operation to a HandlerThread that would do the job in a worker thread asynchronously. First we’ll initialize the HandlerThread and its associated Handler in the constructor.<\/p>\n Then add a custom text watcher:<\/p>\n Then finally in To learn more about HandlerThreads, you should read this article<\/a> that I wrote sometime back.<\/p>\n Make sure the filtering logic, i.e., Now both the internal TextWatcher and the one you add will keep on executing. The internal one will try to filter but since the filtering logic is empty, nothing will happen. The custom one that we added will do the actual filtering by modifying the data source (ArrayList) of the Adapter and triggering the There’s some information regarding where you should store your API Key at the end of this article<\/a> that you should go through once.<\/p>\n The Google Place Autocomplete service makes it really simple to integrate a widget where the user can select his location that the app can store and later use for various purposes. If you think about it carefully, Google’s service is only being used to fetch a set of results in JSON format. So replacing that with your own API service will be fairly easy in terms of Android code, i.e., you’ll just need to make few changes in the References:<\/p>\n Recently in one of my android applications I wanted to obtain the user’s location in terms of city and country that they can feed from their (edit) profile section. So one way to do this is basically have two dropdowns or dialogs (or even open an entirely new activity where the user can search through … Continue reading “Google Place API Autocomplete Service in Android Application”<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[121],"tags":[105,89],"_links":{"self":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/2092"}],"collection":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/comments?post=2092"}],"version-history":[{"count":6,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/2092\/revisions"}],"predecessor-version":[{"id":2100,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/2092\/revisions\/2100"}],"wp:attachment":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/media?parent=2092"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/categories?post=2092"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/tags?post=2092"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}Getting an API Key<\/h2>\n
Know your Limits<\/h2>\n
Place Autocomplete Requests and Responses<\/h2>\n
https:\/\/maps.googleapis.com\/maps\/api\/place\/autocomplete\/output?parameters<\/code> where
output<\/code> can be either
json<\/code> or
xml<\/code> indicating the type of expected response. There are 2 required
GET<\/code> parameters which are
input<\/code> that should contain the string (that the user will type) to use as the search term and
key<\/code> which should contain your API key. So then the URL will look something like this:<\/p>\n
\r\nhttps:\/\/maps.googleapis.com\/maps\/api\/place\/autocomplete\/json?input=ban&key=API_KEY\r\n<\/pre>\n
types<\/code> parameter. You can pass various values to
types<\/code> like:<\/p>\n
\n
geocode<\/code> – Sort of like broad-level (generic) addresses.\n<\/li>\n
address<\/code> – Fully specified addresses.\n<\/li>\n
establishment<\/code> – Business results.\n<\/li>\n
(regions)<\/code> – This will return results matching
locality<\/code>,
sublocality<\/code>,
postal_code<\/code>,
country<\/code>,
administrative_area_level_1<\/code>,
administrative_area_level_2<\/code>. Specific details for each of them can be found here<\/a>.\n<\/li>\n
(cities)<\/code> – Return results matching
locality<\/code>,
administrative_area_level_3<\/code>.\n<\/li>\n<\/ul>\n
\r\nhttps:\/\/maps.googleapis.com\/maps\/api\/place\/autocomplete\/json?input=ban&types=(cities)&key=API_KEY\r\n<\/pre>\n
\r\n{\r\n "predictions" : [\r\n {\r\n "description" : "Bangalore, Karnataka, India",\r\n "id" : "0862832923832bfb1e46cbe843cdaa03a9ee8aa1",\r\n "matched_substrings" : [\r\n {\r\n "length" : 3,\r\n "offset" : 0\r\n }\r\n ],\r\n "place_id" : "ChIJbU60yXAWrjsR4E9-UejD3_g",\r\n "reference" : "CkQzAAAAUY01eTC-f7Z9vOzDiHFtEL0rLEXfgae0MfOPR8bE26gDDFceZ3AH0SNE44HK7v27noYrRKobbNiWQQ2E4HjRPBIQAM_qIcQgybTqiyKiGyZtkhoUiFs2XXjLEJ3jWUU-p_PGcbm8JH4",\r\n "terms" : [\r\n {\r\n "offset" : 0,\r\n "value" : "Bangalore"\r\n },\r\n {\r\n "offset" : 11,\r\n "value" : "Karnataka"\r\n },\r\n {\r\n "offset" : 22,\r\n "value" : "India"\r\n }\r\n ],\r\n "types" : [ "locality", "political", "geocode" ]\r\n },\r\n {\r\n "description" : "Bangarapet, Karnataka, India",\r\n "id" : "7496e1dc83b719d83d1f64dcacc1b0592bc1149b",\r\n "matched_substrings" : [\r\n {\r\n "length" : 3,\r\n "offset" : 0\r\n }\r\n ],\r\n "place_id" : "ChIJu4YuxDfprTsRHSqs7ubXIaw",\r\n "reference" : "CkQ0AAAAGsgimTlmv97VHo27L3dRXg0ieWui67PGx84buBY3byBdBecoLmV-IwnW93-RxFRWgQkYRsM3NCQ38UEoKG_VQBIQe7TArn4qHgds5g_Y1Jhl0hoUe0sawRU7Hazq-4jJ8_0RT5coqBY",\r\n "terms" : [\r\n {\r\n "offset" : 0,\r\n "value" : "Bangarapet"\r\n },\r\n {\r\n "offset" : 12,\r\n "value" : "Karnataka"\r\n },\r\n {\r\n "offset" : 23,\r\n "value" : "India"\r\n }\r\n ],\r\n "types" : [ "locality", "political", "geocode" ]\r\n },\r\n {\r\n "description" : "Bande Nalla Sandra, Karnataka, India",\r\n "id" : "bbdabbca92990bd63dcc6a300d54fdfdcc3f5199",\r\n "matched_substrings" : [\r\n {\r\n "length" : 3,\r\n "offset" : 0\r\n }\r\n ],\r\n "place_id" : "ChIJseY83P9rrjsRj2k7EToO7Qs",\r\n "reference" : "CkQ8AAAA88Q9tpa3SB4oz5t00LapY--mX1kMNOhzO6yQxATNcRTsJvseqWNvu_6xwQm15r55XjV13uvElBa-FHLisNJv-BIQ7jt7wr2lw1Cf4kYuPCXH5BoU3d_k-Oyyh75-uYd-cqsEHZjuCKA",\r\n "terms" : [\r\n {\r\n "offset" : 0,\r\n "value" : "Bande Nalla Sandra"\r\n },\r\n {\r\n "offset" : 20,\r\n "value" : "Karnataka"\r\n },\r\n {\r\n "offset" : 31,\r\n "value" : "India"\r\n }\r\n ],\r\n "types" : [ "locality", "political", "geocode" ]\r\n },\r\n {\r\n "description" : "Bandipur, Karnataka, India",\r\n "id" : "ba9f9c4fb47c1e65e8b8584a80b05c024c9abab5",\r\n "matched_substrings" : [\r\n {\r\n "length" : 3,\r\n "offset" : 0\r\n }\r\n ],\r\n "place_id" : "ChIJDQkpSv2tqDsRIBeczAHjVq0",\r\n "reference" : "CkQyAAAAEwbLyLxCGnxREVqs067O7MIHJuvr_gUTkBxWoGmZ4XX1xOi8x3krYrL5YelYwn-KdFCQwZw2Usrdr_14loGIfRIQc4CkJu7jq8t0K9VHO7viGxoUacOrVQYFfAS5x8s5bkQbZAQJVXQ",\r\n "terms" : [\r\n {\r\n "offset" : 0,\r\n "value" : "Bandipur"\r\n },\r\n {\r\n "offset" : 10,\r\n "value" : "Karnataka"\r\n },\r\n {\r\n "offset" : 21,\r\n "value" : "India"\r\n }\r\n ],\r\n "types" : [ "locality", "political", "geocode" ]\r\n },\r\n {\r\n "description" : "Bangkok Thailand",\r\n "id" : "56ef65d942d42054613887fd09cee596d5949359",\r\n "matched_substrings" : [\r\n {\r\n "length" : 3,\r\n "offset" : 0\r\n }\r\n ],\r\n "place_id" : "ChIJ82ENKDJgHTERIEjiXbIAAQE",\r\n "reference" : "CjQoAAAALKjoDM_fiySdIqLK1_B-lWKsH0CyTmaXChd-XYUs6hejfoflIwFfFvUcj12e9Ae3EhC4qhXFaJMMqAIy7-_TJvKKGhQQ7VifQF1Cpn8-bMXCceX0MaQa9A",\r\n "terms" : [\r\n {\r\n "offset" : 0,\r\n "value" : "Bangkok"\r\n },\r\n {\r\n "offset" : 8,\r\n "value" : "Thailand"\r\n }\r\n ],\r\n "types" : [ "locality", "political", "geocode" ]\r\n }\r\n ],\r\n "status" : "OK"\r\n}\r\n<\/pre>\n
description<\/code> key has the place name which is sort of in the format “city, state, country” or “city, country”. The
place_id<\/code> data can be used to make Place Details Requests<\/a> in which you can get the lat\/long any several other details regarding the place.<\/p>\n
Adding Place Autocomplete to the Android App<\/h2>\n
AutoCompleteTextView<\/code><\/a>. The requests issued are just like any other request from any type of client like a web browser or a server-side programming language.<\/p>\n
Creating the Request Class<\/h3>\n
PlaceAPI<\/code> that will send requests to the API over HTTP and then parse the response JSON to build a list of strings containing the
description<\/code> key which contains the city\/state\/country names (like “Bangalore, Karnataka, India”). Here’s the class code:<\/p>\n
\r\npublic class PlaceAPI {\r\n\r\n private static final String TAG = PlaceAPI.class.getSimpleName();\r\n\r\n private static final String PLACES_API_BASE = "https:\/\/maps.googleapis.com\/maps\/api\/place";\r\n private static final String TYPE_AUTOCOMPLETE = "\/autocomplete";\r\n private static final String OUT_JSON = "\/json";\r\n\r\n private static final String API_KEY = "YOUR_API_KEY";\r\n\r\n public ArrayList<String> autocomplete (String input) {\r\n ArrayList<String> resultList = null;\r\n\r\n HttpURLConnection conn = null;\r\n StringBuilder jsonResults = new StringBuilder();\r\n\r\n try {\r\n StringBuilder sb = new StringBuilder(PLACES_API_BASE + TYPE_AUTOCOMPLETE + OUT_JSON);\r\n sb.append("?key=" + API_KEY);\r\n sb.append("&types=(cities)");\r\n sb.append("&input=" + URLEncoder.encode(input, "utf8"));\r\n\r\n URL url = new URL(sb.toString());\r\n conn = (HttpURLConnection) url.openConnection();\r\n InputStreamReader in = new InputStreamReader(conn.getInputStream());\r\n\r\n \/\/ Load the results into a StringBuilder\r\n int read;\r\n char[] buff = new char[1024];\r\n while ((read = in.read(buff)) != -1) {\r\n jsonResults.append(buff, 0, read);\r\n }\r\n } catch (MalformedURLException e) {\r\n Log.e(TAG, "Error processing Places API URL", e);\r\n return resultList;\r\n } catch (IOException e) {\r\n Log.e(TAG, "Error connecting to Places API", e);\r\n return resultList;\r\n } finally {\r\n if (conn != null) {\r\n conn.disconnect();\r\n }\r\n }\r\n\r\n try {\r\n \/\/ Log.d(TAG, jsonResults.toString());\r\n\r\n \/\/ Create a JSON object hierarchy from the results\r\n JSONObject jsonObj = new JSONObject(jsonResults.toString());\r\n JSONArray predsJsonArray = jsonObj.getJSONArray("predictions");\r\n\r\n \/\/ Extract the Place descriptions from the results\r\n resultList = new ArrayList<String>(predsJsonArray.length());\r\n for (int i = 0; i < predsJsonArray.length(); i++) {\r\n resultList.add(predsJsonArray.getJSONObject(i).getString("description"));\r\n }\r\n } catch (JSONException e) {\r\n Log.e(TAG, "Cannot process JSON results", e);\r\n }\r\n\r\n return resultList;\r\n }\r\n}\r\n<\/pre>\n
res\/layout\/activity_main.xml<\/code>) has an AutoCompleteTextView:<\/p>\n
\r\n<!-- City and Country Selector -->\r\n<AutoCompleteTextView\r\n android:layout_width="match_parent"\r\n android:layout_height="wrap_content"\r\n android:id="@+id\/autocomplete"\r\n android:hint="Type in your Location" \/>\r\n<\/pre>\n
res\/layout\/autocomplete_list_item.xml<\/code>) that’ll hold the views representing each entry inside the AutoCompleteTextView:<\/p>\n
\r\n<?xml version="1.0" encoding="utf-8"?>\r\n<TextView xmlns:android="http:\/\/schemas.android.com\/apk\/res\/android"\r\n android:layout_width="match_parent"\r\n android:layout_height="wrap_content"\r\n android:padding="10dp"\r\n android:id="@+id\/autocompleteText" \/>\r\n<\/pre>\n
\r\nAutoCompleteTextView autocompleteView = (AutoCompleteTextView) rootView.findViewById(R.id.autocomplete);\r\nautocompleteView.setAdapter(new PlacesAutoCompleteAdapter(getActivity(), R.layout.autocomplete_list_item));\r\n<\/pre>\n
PlacesAutoCompleteAdapter<\/code> class now which should be subtype of
ListAdapter<\/code> (extends) and
Filterable<\/code> (implements), if you check the type of parameter specified for the
setAdapter()<\/code> method in the docs<\/a>.<\/p>\n
\r\nclass PlacesAutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {\r\n\r\n ArrayList<String> resultList;\r\n\r\n Context mContext;\r\n int mResource;\r\n \r\n PlaceAPI mPlaceAPI = new PlaceAPI();\r\n\r\n public PlacesAutoCompleteAdapter(Context context, int resource) {\r\n super(context, resource);\r\n\r\n mContext = context;\r\n mResource = resource;\r\n }\r\n\r\n @Override\r\n public int getCount() {\r\n \/\/ Last item will be the footer\r\n return resultList.size();\r\n }\r\n\r\n @Override\r\n public String getItem(int position) {\r\n return resultList.get(position);\r\n }\r\n\r\n @Override\r\n public Filter getFilter() {\r\n Filter filter = new Filter() {\r\n @Override\r\n protected FilterResults performFiltering(CharSequence constraint) {\r\n FilterResults filterResults = new FilterResults();\r\n if (constraint != null) {\r\n resultList = mPlaceAPI.autocomplete(constraint.toString());\r\n\r\n filterResults.values = resultList;\r\n filterResults.count = resultList.size();\r\n }\r\n\r\n return filterResults;\r\n }\r\n\r\n @Override\r\n protected void publishResults(CharSequence constraint, FilterResults results) {\r\n if (results != null && results.count > 0) {\r\n notifyDataSetChanged();\r\n }\r\n else {\r\n notifyDataSetInvalidated();\r\n }\r\n }\r\n };\r\n\r\n return filter;\r\n }\r\n}\r\n<\/pre>\n
filter()<\/code> method on the
Filter<\/code> object returned by
getFilter()<\/code> will be called that’ll trigger
performFiltering()<\/code> (asynchronously in a background thread). This method calls the
autocomplete()<\/code> method on the `PlaceAPI` class which makes the HTTP calls to the Places API returning results for the string\/text entered by the user. The API does a substring match against its database of places and returns a result containing predictions which are basically a list of the places found. The predictions are stored in an
ArrayList<\/code> which are then used to populate the AutoCompleteTextView internally by the
getView()<\/code> method of
ArrayAdapter<\/code>.<\/p>\n
Getting the Selection Made<\/h3>\n
\r\nautocompleteView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\r\n @Override\r\n public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\r\n \/\/ Get data associated with the specified position\r\n \/\/ in the list (AdapterView)\r\n String description = (String) parent.getItemAtPosition(position);\r\n Toast.makeText(getActivity(), description, Toast.LENGTH_SHORT).show();\r\n }\r\n});\r\n<\/pre>\n
Show Powered by Google Logo<\/h3>\n
addHeaderView()<\/code> and
addFooterView()<\/code> methods like the
ListView<\/code><\/a> ViewGroup. So for now, one way to achieve this is by adding an extra element to the ArrayList used as the data source by the adapter and then overriding the
getView()<\/code> method in the Adapter. In the
getView()<\/code> if the
position<\/code> is the last index (equals
list.size() - 1<\/code>) then inflate a layout which contains an
ImageView<\/code> whose
src<\/code> is set to the google logo’s drawable file.<\/p>\n
\r\n@Override\r\npublic View getView(int position, View convertView, ViewGroup parent) {\r\n View view;\r\n\r\n \/\/if (convertView == null) {\r\n LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\r\n\r\n if (position != (resultList.size() - 1))\r\n view = inflater.inflate(R.layout.autocomplete_list_item, null);\r\n else\r\n view = inflater.inflate(R.layout.autocomplete_google_logo, null);\r\n \/\/}\r\n \/\/else {\r\n \/\/ view = convertView;\r\n \/\/}\r\n\r\n if (position != (resultList.size() - 1)) {\r\n TextView autocompleteTextView = (TextView) view.findViewById(R.id.autocompleteText);\r\n autocompleteTextView.setText(resultList.get(position));\r\n }\r\n else {\r\n ImageView imageView = (ImageView) view.findViewById(R.id.imageView);\r\n \/\/ not sure what to do :D\r\n }\r\n\r\n return view;\r\n}\r\n<\/pre>\n
performFiltering()<\/code> method:<\/p>\n
\r\n@Override\r\nprotected FilterResults performFiltering(CharSequence constraint) {\r\n FilterResults filterResults = new FilterResults();\r\n if (constraint != null) {\r\n resultList = mPlaceAPI.autocomplete(constraint.toString());\r\n\r\n \/\/ Footer\r\n resultList.add("footer");\r\n\r\n filterResults.values = resultList;\r\n filterResults.count = resultList.size();\r\n }\r\n\r\n return filterResults;\r\n}\r\n<\/pre>\n
\/\/ Footer<\/code> comment and the line next to it. This should be enough to abide by their logo requirement policies.<\/p>\n
Setting Timeouts for Requests<\/h3>\n
afterTextChanged()<\/code> method keeps on calling the
filter()<\/code> method on the Filter object we supply. Now the
autocomplete()<\/code> method on
PlaceAPI<\/code> must not be called on the UI\/Main thread (since it’s a Network operation) and
performFiltering()<\/code> already gets called on a background thread which must also return a
FilterResults<\/code> instantly. So I guess one way to modify the
performFiltering()<\/code> logic and making it work might be to create a new Handler object inside that (which’ll attach to the background HandlerThread created internally) and then post Runnables to it with a delay. Then return null or an empty
FilterResults<\/code> object which will then call
publishResults<\/code> where you pretty much do nothing. When the internal created handler is done executing, post it to a Handler on the main thread. But I think after waiting for 3 seconds or so, the HandlerThread will quit, so this can be a little messy.<\/p>\n
\r\nprivate static String TAG = MainActivity.class.getSimpleName();\r\n\r\nprivate PlacesAutoCompleteAdapter mAdapter;\r\n\r\nHandlerThread mHandlerThread;\r\nHandler mThreadHandler;\r\npublic ProfileFragment() {\r\n \/\/ Required empty public constructor\r\n\r\n if (mThreadHandler == null) {\r\n \/\/ Initialize and start the HandlerThread\r\n \/\/ which is basically a Thread with a Looper\r\n \/\/ attached (hence a MessageQueue)\r\n mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);\r\n mHandlerThread.start();\r\n\r\n \/\/ Initialize the Handler\r\n mThreadHandler = new Handler(mHandlerThread.getLooper()) {\r\n @Override\r\n public void handleMessage(Message msg) {\r\n if (msg.what == 1) {\r\n ArrayList<String> results = mAdapter.resultList;\r\n\r\n if (results != null && results.size() > 0) {\r\n mAdapter.notifyDataSetChanged();\r\n }\r\n else {\r\n mAdapter.notifyDataSetInvalidated();\r\n }\r\n }\r\n }\r\n };\r\n }\r\n\r\n}\r\n<\/pre>\n
\r\nautocompleteView.addTextChangedListener(new TextWatcher() {\r\n @Override\r\n public void beforeTextChanged(CharSequence s, int start, int count, int after) {\r\n\r\n }\r\n\r\n @Override\r\n public void onTextChanged(CharSequence s, int start, int before, int count) {\r\n final String value = s.toString();\r\n\r\n \/\/ Remove all callbacks and messages\r\n mThreadHandler.removeCallbacksAndMessages(null);\r\n\r\n \/\/ Now add a new one\r\n mThreadHandler.postDelayed(new Runnable() {\r\n\r\n @Override\r\n public void run() {\r\n \/\/ Background thread\r\n\r\n mAdapter.resultList = mAdapter.mPlaceAPI.autocomplete(value);\r\n\r\n \/\/ Footer\r\n if (mAdapter.resultList.size() > 0)\r\n mAdapter.resultList.add("footer");\r\n\r\n \/\/ Post to Main Thread\r\n mThreadHandler.sendEmptyMessage(1);\r\n }\r\n }, 500);\r\n }\r\n\r\n @Override\r\n public void afterTextChanged(Editable s) {\r\n doAfterTextChanged();\r\n }\r\n});\r\n<\/pre>\n
onDestroy()<\/code> we should quit the HandlerThread:<\/p>\n
\r\n@Override\r\npublic void onDestroy() {\r\n super.onDestroy();\r\n\r\n \/\/ Get rid of our Place API Handlers\r\n if (mThreadHandler != null) {\r\n mThreadHandler.removeCallbacksAndMessages(null);\r\n mHandlerThread.quit();\r\n }\r\n}\r\n<\/pre>\n
performFiltering()<\/code> and
publishResults()<\/code> are empty. You can return null or an empty
FilterResults<\/code> object from
performFiltering()<\/code>.<\/p>\n
notifyDataSetChanged()<\/code>.<\/p>\n
API Keys<\/h3>\n
Summary<\/h2>\n
PlaceAPI<\/code> class. This is the power of separation and abstraction. If you think you’ll surpass Google’s request limit or cannot afford them, then consider building your own service using some free\/paid databases provided by various vendors online.<\/p>\n
\n