42

I want to periodically change the suggestions given by an AutoCompleteTextview by getting the list from a RESTful web service, and can't get it working smoothly. I set up a hard-coded list of suggestions to make sure it's working:

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, new String[] {"Hi", "Ho"});
speciesName.setAdapter(adapter);//my autocomplete tv

I have got a TextWatcher on the textview and when the text changes that launches a non-blocking call to get a new list of suggestions -- this part which gets a new list is working fine. Then I want to reset the adapter, like so:

public void setOptionsAndUpdate(String[] options) {
    Log.d(TAG, "setting options");
    //speciesName.setAdapter((ArrayAdapter<String>)null);
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, options);
    speciesName.setAdapter(adapter);
}

This method is called, but doesn't work -- the list of suggestions either disappears or the displayed suggestions remain unchanged despite the call to setAdapter.

Is this even the right approach? I looked at SimpleCursorAdapter but couldn't see how to register my web service as a content provider. (It's of the form http://www.blah.com/query?term=XX, where the XX is the input from my app, and the response is a JSON Array of strings.)

Jonik
  • 80,077
  • 70
  • 264
  • 372
jaybee
  • 1,897
  • 2
  • 16
  • 29
  • I am doing something similar HERE!!! http://stackoverflow.com/questions/12854336/autocompletetextview-backed-by-cursorloader – Etienne Lawlor Oct 29 '12 at 20:08

5 Answers5

61

I didn't have any luck using adapter.notifyDataSetChanged() when dynamically adding and changing the data in the adapter. In my situation, I was hitting an external api asynchronously and getting a list of completion data periodically.

This code clears the adapter, and adds the new data as you'd expect. However, I had to call the getFilter().Filter method to force the data to show. Also, I had to explicitly filter based on the current text in the AutocompleteTextView because my api call was asynchronous.

adapter.clear();
for (Map<String, String> map : completions) {
     adapter.add(map.get("name"));
}

//Force the adapter to filter itself, necessary to show new data.
//Filter based on the current text because api call is asynchronous. 
adapter.getFilter().filter(autocompleteTextView.getText(), null);
GLee
  • 5,003
  • 5
  • 35
  • 39
  • 2
    instead of passing in `null` you can pass in the `autocompleteTextView` to have it behave in the same way that `AutoCompleteTextView.performFiltering()` does - which is the method usually called after the text is changed – kassim Mar 01 '15 at 16:53
  • 1
    Spending hours searching for this. I own you a beer :) – Dũng Trần Trung Dec 17 '15 at 18:21
  • this is the best solution for this sort of problem! Meny thanks! – Денис Чорный Dec 16 '20 at 05:22
  • That's the only way I could get an update of the autocompletion list after updating it from within `doAfterTextChanged`, where I asynchronously fetch the relevant list from a DB query. And it avoids creating an adapter each time. Thanks! – RedGlyph Jan 06 '22 at 10:19
  • note: you can now manually refresh the suggestions by invoking: `AutoCompleteTextView.refreshAutoCompleteResults()` (since API Level 29) see https://developer.android.com/reference/android/widget/AutoCompleteTextView#refreshAutoCompleteResults() – Chris_D_Turk Sep 01 '22 at 14:16
25

There was a pretty good tutorial on this topic using remote data in the Google Map API to populate a AutoCompleteTextView here.

If you need a cached version, I retrieved it from here. The original tutorial has been deleted, but essentially you need to write an ArrayAdapter with a custom filter in a similar way to that shown below and assign it to your AutoCompleteTextView.

Note: You need to implement a method autocomplete() that does whatever operation is required to synchronously fetch and return the autocompletion items. As the filter is invoked in a background thread, this will not block the main UI thread.

private class PlacesAutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {
private ArrayList<String> resultList;

public PlacesAutoCompleteAdapter(Context context, int textViewResourceId) {
    super(context, textViewResourceId);
}

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

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

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

                // Assign the data to the FilterResults
                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;
}
}
Sibren
  • 1,068
  • 11
  • 11
  • I had the same question about UI thread. @manyobject can you clerify? – Hadi Tok Nov 28 '14 at 12:46
  • 1
    I tested it and didn't received NetworkOnMainThreadException. So its safe to use. – Hadi Tok Nov 30 '14 at 16:09
  • 1
    The network call doesn't block the UI because Android performs the filtering on a background asynchronous thread. So, you can/should write the autocomplete() method so that it acquires data synchronously (blocking). –  Apr 29 '15 at 16:50
  • This should be accepted answer!!! Well done. Verified this with Retrofit synchronous call. Works without blocking UI thread!! – AkshayT Apr 04 '18 at 15:07
  • Thanks, it works! :) One suggestion: it may cause IndexOutOfBoundsException, if you type fast (resultList is updating). Solution: update your local resultList in function publishResults() instead of performFiltering() – steve Mar 03 '20 at 08:06
24

This is how I update my AutoCompleteTextView:

String[] data = terms.toArray(new String[terms.size()]);  // terms is a List<String>
ArrayAdapter<?> adapter = new ArrayAdapter<Object>(activity, android.R.layout.simple_dropdown_item_1line, data);
keywordField.setAdapter(adapter);  // keywordField is a AutoCompleteTextView
if(terms.size() < 40) keywordField.setThreshold(1); 
else keywordField.setThreshold(2);

Now of course, this is static and doesn't deal with an over-the-air suggestions but, I can also suggest you to notify adapter for the changes after you assign it to the AutoCompleteTextView:

adapter.notifyDataSetChanged();   

Hope this helps.

-serkan

serkanozel
  • 2,947
  • 1
  • 23
  • 27
  • @serkan: Great, this is what I needed. However, I think the threshold is not updated, right? So in your example, if `terms.size() < 40` at the beginning, the threshold will stay in 1 forever. Of course you can run that line of code again to update. Thanks! – Luis A. Florit Dec 08 '12 at 19:41
  • Thanks Luis. Well, what I'm doing there is just saying, if I have a small list of things, I can start suggesting even after 1 character's entered; however if my pool is large, then, I should wait until two characters are entered for me to suggest anything - for efficiency purposes. You certainly keep it 1, or 2 at all times. – serkanozel Dec 08 '12 at 22:02
2

Since i am not able to add a comment, i am giving a new answer There is no need for clearing the adapter or calling adapter.getFilter().filter(...)... To dynamically update an AutoCompleteTextView adapter, simply add the new item to the adapter and setAdapter again. For the example given in the original question, i tried the following and it works (the code below does not show the initial setting of the adapter, since multiple answers here cover that. This just shows updating the adapter dynamically). The adapter update can be alongside the code that updates the List associated with the ArrayAdapter.

    adapter.add(String newSuggestion); // this goes inside a loop for adding multiple suggestions
    speciesName.setAdapter(adapter) ; // speciesName is an AutoCompleteTextView as given in the original question.
sri_s
  • 103
  • 1
  • 9
1

The best solution I found for updating the adapter:

Editable text = autocomplete.getText();
autocomplete.setText(text);
autocomplete.setSelection(text.length());

How it works:

We set the text of autoCompleteTextView with its current text, so the adapter notifies that data is changed and updates the listViews's content.

But by this trick the cursor moves to the beginning of edittext. so we use autocomplete.setSelection(text.length()) for moving the cursor to the end.

Works like a charm!

Edit:

Also you must use clear(), add() and remove() methods directly on your ArrayAdapter instead of your ArrayList.

Homayoon Ahmadi
  • 1,181
  • 1
  • 12
  • 24
  • 1
    I tried many different solutions, but this one was the only solution that worked for me. – Matt Apr 18 '19 at 06:50