Google Place API Autocomplete Service in Android Application

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.

Anyway, while searching the web and trying out apps like Airbnb and Couchsurfing I figured Google has the Place Autocomplete service 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.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Google Place Autocomplete API Android

So let’s see how to execute this.

Getting an API Key

The first thing to do is to get an API key from Google Developers Console. More on this here. 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.

Know your Limits

You should know about the usage limitation. All the information pertaining to that is available here.

Place Autocomplete Requests and Responses

You should go through the Place Autocomplete service documentation where you can learn everything about the requests and responses. Basically you’ll have to make a request to https://maps.googleapis.com/maps/api/place/autocomplete/output?parameters where output can be either json or xml indicating the type of expected response. There are 2 required GET parameters which are input that should contain the string (that the user will type) to use as the search term and key which should contain your API key. So then the URL will look something like this:

https://maps.googleapis.com/maps/api/place/autocomplete/json?input=ban&key=API_KEY

There are other optional parameters that you can read up on but the most important one that you’ll mostly use is the types parameter. You can pass various values to types like:

  • geocode – Sort of like broad-level (generic) addresses.
  • address – Fully specified addresses.
  • establishment – Business results.
  • (regions) – This will return results matching locality, sublocality, postal_code, country, administrative_area_level_1, administrative_area_level_2. Specific details for each of them can be found here.
  • (cities) – Return results matching locality, administrative_area_level_3.

You can try a JavaScript based Place Autocomplete demo here. In our case we only want cities, so here’s a sample request:

https://maps.googleapis.com/maps/api/place/autocomplete/json?input=ban&types=(cities)&key=API_KEY

Here’s the type of response that you should expect:

{
   "predictions" : [
      {
         "description" : "Bangalore, Karnataka, India",
         "id" : "0862832923832bfb1e46cbe843cdaa03a9ee8aa1",
         "matched_substrings" : [
            {
               "length" : 3,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJbU60yXAWrjsR4E9-UejD3_g",
         "reference" : "CkQzAAAAUY01eTC-f7Z9vOzDiHFtEL0rLEXfgae0MfOPR8bE26gDDFceZ3AH0SNE44HK7v27noYrRKobbNiWQQ2E4HjRPBIQAM_qIcQgybTqiyKiGyZtkhoUiFs2XXjLEJ3jWUU-p_PGcbm8JH4",
         "terms" : [
            {
               "offset" : 0,
               "value" : "Bangalore"
            },
            {
               "offset" : 11,
               "value" : "Karnataka"
            },
            {
               "offset" : 22,
               "value" : "India"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      },
      {
         "description" : "Bangarapet, Karnataka, India",
         "id" : "7496e1dc83b719d83d1f64dcacc1b0592bc1149b",
         "matched_substrings" : [
            {
               "length" : 3,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJu4YuxDfprTsRHSqs7ubXIaw",
         "reference" : "CkQ0AAAAGsgimTlmv97VHo27L3dRXg0ieWui67PGx84buBY3byBdBecoLmV-IwnW93-RxFRWgQkYRsM3NCQ38UEoKG_VQBIQe7TArn4qHgds5g_Y1Jhl0hoUe0sawRU7Hazq-4jJ8_0RT5coqBY",
         "terms" : [
            {
               "offset" : 0,
               "value" : "Bangarapet"
            },
            {
               "offset" : 12,
               "value" : "Karnataka"
            },
            {
               "offset" : 23,
               "value" : "India"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      },
      {
         "description" : "Bande Nalla Sandra, Karnataka, India",
         "id" : "bbdabbca92990bd63dcc6a300d54fdfdcc3f5199",
         "matched_substrings" : [
            {
               "length" : 3,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJseY83P9rrjsRj2k7EToO7Qs",
         "reference" : "CkQ8AAAA88Q9tpa3SB4oz5t00LapY--mX1kMNOhzO6yQxATNcRTsJvseqWNvu_6xwQm15r55XjV13uvElBa-FHLisNJv-BIQ7jt7wr2lw1Cf4kYuPCXH5BoU3d_k-Oyyh75-uYd-cqsEHZjuCKA",
         "terms" : [
            {
               "offset" : 0,
               "value" : "Bande Nalla Sandra"
            },
            {
               "offset" : 20,
               "value" : "Karnataka"
            },
            {
               "offset" : 31,
               "value" : "India"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      },
      {
         "description" : "Bandipur, Karnataka, India",
         "id" : "ba9f9c4fb47c1e65e8b8584a80b05c024c9abab5",
         "matched_substrings" : [
            {
               "length" : 3,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJDQkpSv2tqDsRIBeczAHjVq0",
         "reference" : "CkQyAAAAEwbLyLxCGnxREVqs067O7MIHJuvr_gUTkBxWoGmZ4XX1xOi8x3krYrL5YelYwn-KdFCQwZw2Usrdr_14loGIfRIQc4CkJu7jq8t0K9VHO7viGxoUacOrVQYFfAS5x8s5bkQbZAQJVXQ",
         "terms" : [
            {
               "offset" : 0,
               "value" : "Bandipur"
            },
            {
               "offset" : 10,
               "value" : "Karnataka"
            },
            {
               "offset" : 21,
               "value" : "India"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      },
      {
         "description" : "Bangkok Thailand",
         "id" : "56ef65d942d42054613887fd09cee596d5949359",
         "matched_substrings" : [
            {
               "length" : 3,
               "offset" : 0
            }
         ],
         "place_id" : "ChIJ82ENKDJgHTERIEjiXbIAAQE",
         "reference" : "CjQoAAAALKjoDM_fiySdIqLK1_B-lWKsH0CyTmaXChd-XYUs6hejfoflIwFfFvUcj12e9Ae3EhC4qhXFaJMMqAIy7-_TJvKKGhQQ7VifQF1Cpn8-bMXCceX0MaQa9A",
         "terms" : [
            {
               "offset" : 0,
               "value" : "Bangkok"
            },
            {
               "offset" : 8,
               "value" : "Thailand"
            }
         ],
         "types" : [ "locality", "political", "geocode" ]
      }
   ],
   "status" : "OK"
}

You’ll note that the description key has the place name which is sort of in the format “city, state, country” or “city, country”. The place_id data can be used to make Place Details Requests in which you can get the lat/long any several other details regarding the place.

Adding Place Autocomplete to the Android App

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 AutoCompleteTextView. The requests issued are just like any other request from any type of client like a web browser or a server-side programming language.

Creating the Request Class

We’ll create a class called PlaceAPI that will send requests to the API over HTTP and then parse the response JSON to build a list of strings containing the description key which contains the city/state/country names (like “Bangalore, Karnataka, India”). Here’s the class code:

public class PlaceAPI {

    private static final String TAG = PlaceAPI.class.getSimpleName();

    private static final String PLACES_API_BASE = "https://maps.googleapis.com/maps/api/place";
    private static final String TYPE_AUTOCOMPLETE = "/autocomplete";
    private static final String OUT_JSON = "/json";

    private static final String API_KEY = "YOUR_API_KEY";

    public ArrayList<String> autocomplete (String input) {
        ArrayList<String> resultList = null;

        HttpURLConnection conn = null;
        StringBuilder jsonResults = new StringBuilder();

        try {
            StringBuilder sb = new StringBuilder(PLACES_API_BASE + TYPE_AUTOCOMPLETE + OUT_JSON);
            sb.append("?key=" + API_KEY);
            sb.append("&types=(cities)");
            sb.append("&input=" + URLEncoder.encode(input, "utf8"));

            URL url = new URL(sb.toString());
            conn = (HttpURLConnection) url.openConnection();
            InputStreamReader in = new InputStreamReader(conn.getInputStream());

            // Load the results into a StringBuilder
            int read;
            char[] buff = new char[1024];
            while ((read = in.read(buff)) != -1) {
                jsonResults.append(buff, 0, read);
            }
        } catch (MalformedURLException e) {
            Log.e(TAG, "Error processing Places API URL", e);
            return resultList;
        } catch (IOException e) {
            Log.e(TAG, "Error connecting to Places API", e);
            return resultList;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        try {
            // Log.d(TAG, jsonResults.toString());

            // Create a JSON object hierarchy from the results
            JSONObject jsonObj = new JSONObject(jsonResults.toString());
            JSONArray predsJsonArray = jsonObj.getJSONArray("predictions");

            // Extract the Place descriptions from the results
            resultList = new ArrayList<String>(predsJsonArray.length());
            for (int i = 0; i < predsJsonArray.length(); i++) {
                resultList.add(predsJsonArray.getJSONObject(i).getString("description"));
            }
        } catch (JSONException e) {
            Log.e(TAG, "Cannot process JSON results", e);
        }

        return resultList;
    }
}

Next, make sure your layout file (for instance res/layout/activity_main.xml) has an AutoCompleteTextView:

<!-- City and Country Selector -->
<AutoCompleteTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/autocomplete"
    android:hint="Type in your Location" />

Also create another layout file (res/layout/autocomplete_list_item.xml) that’ll hold the views representing each entry inside the AutoCompleteTextView:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:id="@+id/autocompleteText" />

Pretty cool! Now your Activity/Fragment class must set the adapter for the AutoCompleteTextView:

AutoCompleteTextView autocompleteView = (AutoCompleteTextView) rootView.findViewById(R.id.autocomplete);
autocompleteView.setAdapter(new PlacesAutoCompleteAdapter(getActivity(), R.layout.autocomplete_list_item));

It’s time to code the PlacesAutoCompleteAdapter class now which should be subtype of ListAdapter (extends) and Filterable (implements), if you check the type of parameter specified for the setAdapter() method in the docs.

class PlacesAutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {

    ArrayList<String> resultList;

    Context mContext;
    int mResource;
    
    PlaceAPI mPlaceAPI = new PlaceAPI();

    public PlacesAutoCompleteAdapter(Context context, int resource) {
        super(context, resource);

        mContext = context;
        mResource = resource;
    }

    @Override
    public int getCount() {
        // Last item will be the footer
        return resultList.size();
    }

    @Override
    public String getItem(int position) {
        return resultList.get(position);
    }

    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                if (constraint != null) {
                    resultList = mPlaceAPI.autocomplete(constraint.toString());

                    filterResults.values = resultList;
                    filterResults.count = resultList.size();
                }

                return filterResults;
            }

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

        return filter;
    }
}

As the user types something inside the AutoCompleteTextView, the filter() method on the Filter object returned by getFilter() will be called that’ll trigger performFiltering() (asynchronously in a background thread). This method calls the autocomplete() 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 which are then used to populate the AutoCompleteTextView internally by the getView() method of ArrayAdapter.

You should go through the AutoCompleteTextView source code if you feel like. Also I’ve written an article 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.

Getting the Selection Made

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).

autocompleteView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // Get data associated with the specified position
        // in the list (AdapterView)
        String description = (String) parent.getItemAtPosition(position);
        Toast.makeText(getActivity(), description, Toast.LENGTH_SHORT).show();
    }
});

Show Powered by Google Logo

According to the Places API Policies, 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 that you can download and use.

So how to show up the logo in the autocomplete list ? AutoCompleteTextView doesn’t have addHeaderView() and addFooterView() methods like the ListView 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() method in the Adapter. In the getView() if the position is the last index (equals list.size() - 1) then inflate a layout which contains an ImageView whose src is set to the google logo’s drawable file.

In practise, this is how a quick modification to our adapter implementation would look like:

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

    //if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if (position != (resultList.size() - 1))
            view = inflater.inflate(R.layout.autocomplete_list_item, null);
        else
            view = inflater.inflate(R.layout.autocomplete_google_logo, null);
    //}
    //else {
    //    view = convertView;
    //}

    if (position != (resultList.size() - 1)) {
        TextView autocompleteTextView = (TextView) view.findViewById(R.id.autocompleteText);
        autocompleteTextView.setText(resultList.get(position));
    }
    else {
        ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
        // not sure what to do 😀
    }

    return view;
}

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 performFiltering() method:

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    FilterResults filterResults = new FilterResults();
    if (constraint != null) {
        resultList = mPlaceAPI.autocomplete(constraint.toString());

        // Footer
        resultList.add("footer");

        filterResults.values = resultList;
        filterResults.count = resultList.size();
    }

    return filterResults;
}

Notice the // Footer comment and the line next to it. This should be enough to abide by their logo requirement policies.

Setting Timeouts for Requests

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.

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 afterTextChanged() method keeps on calling the filter() method on the Filter object we supply. Now the autocomplete() method on PlaceAPI must not be called on the UI/Main thread (since it’s a Network operation) and performFiltering() already gets called on a background thread which must also return a FilterResults instantly. So I guess one way to modify the performFiltering() 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 object which will then call publishResults 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.

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.

private static String TAG = MainActivity.class.getSimpleName();

private PlacesAutoCompleteAdapter mAdapter;

HandlerThread mHandlerThread;
Handler mThreadHandler;
public ProfileFragment() {
    // Required empty public constructor

    if (mThreadHandler == null) {
        // Initialize and start the HandlerThread
        // which is basically a Thread with a Looper
        // attached (hence a MessageQueue)
        mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();

        // Initialize the Handler
        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 1) {
                    ArrayList<String> results = mAdapter.resultList;

                    if (results != null && results.size() > 0) {
                        mAdapter.notifyDataSetChanged();
                    }
                    else {
                        mAdapter.notifyDataSetInvalidated();
                    }
                }
            }
        };
    }

}

Then add a custom text watcher:

autocompleteView.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        final String value = s.toString();

        // Remove all callbacks and messages
        mThreadHandler.removeCallbacksAndMessages(null);

        // Now add a new one
        mThreadHandler.postDelayed(new Runnable() {

            @Override
            public void run() {
                // Background thread

                mAdapter.resultList = mAdapter.mPlaceAPI.autocomplete(value);

                // Footer
                if (mAdapter.resultList.size() > 0)
                    mAdapter.resultList.add("footer");

                // Post to Main Thread
                mThreadHandler.sendEmptyMessage(1);
            }
        }, 500);
    }

    @Override
    public void afterTextChanged(Editable s) {
        doAfterTextChanged();
    }
});

Then finally in onDestroy() we should quit the HandlerThread:

@Override
public void onDestroy() {
    super.onDestroy();

    // Get rid of our Place API Handlers
    if (mThreadHandler != null) {
        mThreadHandler.removeCallbacksAndMessages(null);
        mHandlerThread.quit();
    }
}

To learn more about HandlerThreads, you should read this article that I wrote sometime back.

Make sure the filtering logic, i.e., performFiltering() and publishResults() are empty. You can return null or an empty FilterResults object from performFiltering().

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 notifyDataSetChanged().

API Keys

There’s some information regarding where you should store your API Key at the end of this article that you should go through once.

Summary

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 PlaceAPI 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.

References:

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

30 thoughts on “Google Place API Autocomplete Service in Android Application”

  1. I have a question though.
    How can you call mAdapter.notifyDataSetChanged(); from a background thread?
    That will throw an exception.

    1. I’m not sure whether the question is in the article’s context or general but I’ll answer both.

      Firstly, you should always call that method from the main thread, can’t call from UI thread.

      Secondly, in the context of this article, publishResults() is executed on the main thread. I’ve written an article on filters that can be found here. You’ll also find the notifyDataSetChanged() in the Handler but in that case the Handler is defined in the main thread, hence the handleMessage() method will be called on the main thread. More information in this article.

      1. I was talking about the HandlerThread (mHandlerThread) which you use to get a Handler (mThreadHandler) by writing “new Handler(mHandlerThread.getLooper())”.

        As far as I know, instantiating a HandlerThread, starts a new (different from UI) thread. Getting a handler from HandlerThread mHandleThread’s looper ties it with that thread.

        Am I mistaken?

      2. Hello again,

        Do you mind answering to the reply I gave you please? I’m trying to implement something based on this but I’m having a tough time understanding how the piece of code I was talking about works.

      3. Why don’t you read the articles I pointed in my comment ? HandlerThread executes your code in a different thread, that is correct. But the adapter is changed inside the Handler’s (not HandlerThread’s) handlerMessage() method which is bound to the main thread since the Handler was “created” in the main thread.

        1. Actually, I have read almost all of your posts.
          But I think we are not understanding each other.


          HandlerThread thread = new HandlerThread("newThread");
          thread.start();

          Handler mHandler = new Handler(thread.getLooper()) {
          @Override
          public void handleMessage(Message msg) {
          // Check too seee which thread this code is running on

          if (Looper.myLooper() == Looper.getMainLooper()){
          System.out.println("THIS IS RUNNING ON MAIN THREAD");
          }else{
          System.out.println("THIS IS NOT RUNNING ON MAIN THREAD");
          }

          }
          };

          Basically, what you are telling me is that the piece of code above (that is identical to the code you wrote in this article) executes in the main thread ( the thread in which mHandler was created) when in fact, it does not.

          Everywhere I tested the piece of code above, console prints “THIS IS NOT RUNNING ON MAIN THREAD”. Can you please clue me in as to why ?

        2. Ok, I know what you mean now, given your if/else condition. So according to your if/else condition (I’ve also tested), the code is actually not running on the main thread. To me this is weird due to a couple of observations. You can notify data set change on the adapter or even show a toast message in handleMessage(). These are operations that you’re only allowed to do on a main thread if I’m not mistaken.

          At the same time your if/else shows that the thread is not the UI thread. Also if I run an infinite loop, the app won’t crash (which should happen if the thread is the main one).

          I’m not sure what am I missing, at least my understand is/was, that the handleMessage() will be called on the thread in which the Handler is defined. Now when I go through the source code of Android’s Handler class I cannot verify my assumptions since I’m unable to find the exact place from which the message is supposed to be dispatched to handleMessage(). I might spend some time inspecting this later.

          It is quite possible that handleMessage() should be called on the same thread as the one passed to it in the constructor (HandlerThread’s thread in this case), i.e., the same as the one used to do the execution of the Runnable, but then why should notifications on the adapter or Toast message work ?

          Why don’t you take it up on StackOverflow or some other Android coding related forum ? It could presumably be a bug in Android itself or our understanding is wrong ? I’d also eagerly like to know the answer to this problem.

          1. I don’t know have much experience with Threads, Loopers and Message Queues, but from what I’ve been able to find out almost with certainty is that if you declare
            Handler mHandler = new Handler(mBackgroundThread.getLooper()); that handler will be associated with the mBackgroundThread. You can actually set a TAG for mBackgroundThread and then check the TAG of the currently running thread inside onHandleMessage and you will see it’s the same.

            Now about the notifyDataSetChanged(). What I found out is that (from testing your code above) when you call:

            mAdapter.resultList = mAdapter.mPlaceAPI.autocomplete(value);

            Because resultList is the dataset for your adapter, everytime it is changed, the adapter automatically calls its notifyDataSetChanged() method from whatever thread the adapter is on. That is a fact that you can verify. Everytime you change the dataset of the adapter, the adapter automatically calls this method to update itself without the need for it to call yourself.

            I know this doesn’t explain the ability to call it yourself or to show a Toast message (I have not tested these things) but I wanted to let you know of this. I will post a question to stackoverlow soon and let you know if I find anything. I’d appreciate it if you could do the same.

            Thanks!

  2. Hi, thank you for this tutorial.
    I just want to know how to get API key. I should select the “Create a browser key”, and then leave blank the Accept requests from these HTTP referers (web sites), right? Should I create a server key too?
    Thank you in advance.

    1. Ohh i just saw that there is an api called “Google places for android”. Can you post a tutorial using that, or everything is the same with that?

      1. Can you attach the source code of this project? I am a beginner in android so it would be really helpful. Thank you in advance.

  3. When i added “Setting Timeouts for Requests ” code in addTextChangedListener then i am getting crash on the following lines if no internet is available. Please help me out.
    if (results != null && results.size() > 0) {
    mAdapter.notifyDataSetChanged();
    }

    1. Sorry not that place. I am getting error here
      if (mAdapter.resultList.size() > 0)
      mAdapter.resultList.add(“footer”);

  4. The Filter method in the AutoCompleteAdapter, what do I need to import for that? Android studio gives me multiple choices, but I don’t know which one to import.

  5. Hi,
    I followed your tutorial and it worked, except one thing:
    When I click on the last item in drop down box (the footer with google logo) the autocomplete text will be set to “footer” (as we added in the handler’s background thread). Actually, I think it’s just a footer, not an item, so it should be non-clickable.
    I tried to set clickable to false but it didn’t work.
    So, do you have any idea how can we prevent click event in the footer item?
    Thanks.

  6. Thanks for your quick reply!
    I added these code view.setEnabled(false); view.setOnClickListener(null); and it worked.
    I applied viewholder pattern so I have to add these code above twice.
    Btw I didn’t find out how to applied the second approach (add footer like listview). Any idea about it?
    Thank you.

  7. Thanks for the useful post. I managed to get things mostly working although I was unable to implement the “Setting Timeouts for Requests” section, this isn’t very clear IMO. I attempted to implement it as suggested but like a previous comment mentions, it throws an exception at mAdapter.notifyDataSetChanged();

    This is the exception: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

    Maybe if you can you could clear things up in that section if possible??

    Regardless, thanks again!

  8. hi,

    i am fairly new to android thus i am having a little hard time figuring where to put what . on top of it, what is doAfterTextChanged() method , do i have to write on my own or u just forgot to post.

    it’ll be a great help if you could post the complete code or can simply tell me where to paste what.

    thank u.

  9. Thanks for the post Rishabh. It’s very useful.

    My question is about using the Browser key for a mobile app.
    The browser key, if saved on the mobile app, can be compromised. That is why when you create the Browser key, Google asks you to add HTTP referrers/ websites and warns you: “Other applications might be able to use this key.” if you don’t add the referrers. Correct me if wrong.

  10. Hey, in the .addTextChangedListener in the run() methode, I get a NullPointerException from

    mAdapter.resultList = mAdapter.mPlaceAPI.autocomplete(value);

    where it says “Attempt to read from field ‘….PlaceAPI at …MyActivity$PlacesAutoCompleteAdapter.mPlaceAPI’ on a null object reference’

    Could someone please help me, I would be really glad 🙂

  11. Your code is buggy and has thread excpetions on newer versions of Android Studio.
    I struggled with two exceptions and therfore had to look somewhere else for the answer

    java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification

    java.Networkonmainthreadexception

Leave a Reply

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

*