I have an AutoCompleteTextView with a custom ArrayAdapter that uses ArrayList<Product>.
I've came to the conclusion that a custom ArrayAdapter of an AutoCompleteTextView must implements Filterable and you have to make your own Filtering.
From this SO-question & accepted answer and this example, I have made the following ArrayAdapter:
public class AutoCompleteAdapter extends ArrayAdapter<Product> implements Filterable
{
// Logcat tag
private static final String TAG = "AutoCompleteAdapter";
// The OrderedProductItem we need to get the Filtered ProductNames
OrderedProductItem orderedProductItem;
private Context context;
private ArrayList<Product> productsShown, productsAll;
// Default Constructor for an ArrayAdapter
public AutoCompleteAdapter(Context c, int layoutId, ArrayList<Product> objects, OrderedProductItem opi){
// Though we don't use the Layout-ResourceID , we still need it for the super
super(c, layoutId, objects);
L.Log(TAG, "AutoCompleteAdapter Constructor", LogType.VERBOSE);
// ArrayAdapter's setNotifyOnChange is true by default,
// but I set it nonetheless, just in case
setNotifyOnChange(true);
context = c;
replaceList(objects, true);
orderedProductItem = opi;
}
// Setup the ListItem's UI-elements
#Override
public View getView(int position, View convertView, ViewGroup parent){
return createTextViewAsItem(position);
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent){
return createTextViewAsItem(position);
}
// To prevent repetition, we have this private method
private TextView createTextViewAsItem(int position){
TextView label = new TextView(context);
String name = "";
if(productsShown != null && productsShown.size() > 0 && position >= 0 && position < productsShown.size() - 1)
name = productsShown.get(position).getName();
label.setText(name);
return label;
}
// Replace the List
// When the boolean is set, we replace this ArrayAdapter's List entirely,
// instead of just the filtering
#SuppressWarnings("unchecked")
public void replaceList(ArrayList<Product> p, boolean replaceInitialList){
if(p != null && p.size() > 0){
productsShown = p;
if(replaceInitialList)
productsAll = (ArrayList<Product>)productsShown.clone();
notifyDataSetChanged();
}
}
// Since we are using an AutoCompleteTextView, the Filtering has been reset and we need to apply this ourselves..
Filter filter = new Filter(){
#Override
public String convertResultToString(Object resultValue){
return ((Product)resultValue).getName();
}
#Override
protected FilterResults performFiltering(CharSequence constraint){
FilterResults filterResults = new FilterResults();
if(productsAll != null){
// If no constraint is given, return the whole list
if(constraint == null){
filterResults.values = productsAll;
filterResults.count = productsAll.size();
}
else if(V.notNull(constraint.toString(), true)){
L.Log(TAG, "performFiltering: " + constraint.toString(), LogType.VERBOSE);
ArrayList<Product> suggestions = new ArrayList<Product>();
if(p.size() > 0)
for(Product p : productsAll)
if(p.getName().toLowerCase(Locale.ENGLISH).contains(constraint.toString().toLowerCase(Locale.ENGLISH)))
suggestions.add(p);
filterResults.values = suggestions;
filterResults.count = suggestions.size();
}
}
return filterResults;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if(results != null && results.count > 0)
replaceList((ArrayList<Product>)results.values, false);
}
};
#Override
public Filter getFilter(){
return filter;
}
}
Everything works perfectly. However, since I have a list of around 1250 Products and these are all looped every time the User changes his input in the AutoCompleteTextView, including the creation of two new instantiations (FilterResults and ArrayList), I was wondering if there is a better solution for this without having to loop though everything on every user input change.
If there isn't I just keep this. I was just wondering since with an AutoCompleteTextView containing around 1250 objects, with a custom ArrayAdapter (including custom Filtering) and a custom TextWatcher, it isn't that good for the performance. Especially since this AutoCompleteTextView is used inside the item of a ListView. Which means I have an AutoCompleteTextView for every item (potentially ranging from ~ 5 to 50, with an average of around 15).
This is coming fairly late, however I thought I'd weigh in on your problem...mostly because the rather scaring things occurring with your implementation. To answer your immediate question, there's not much you can easily do to avoid the full ArrayList iteration when filtering. If you need something faster, you'll need to look into pre-processing your data into something with faster search times. AutoComplete Algorithm?.
I have a general rule of thumb for customizing the ArrayAdapter filtering logic. Don't do it. Whenever you run into this situation, the correct solution is to roll your own adapter solution (Using BaseAdapter)...or find a 3rd party solution that allows you too. Part of the issue is that internally the ArrayAdapter has it's own two lists for filtering and it's own internal synchronized lock. Your AutoCompleteAdapter is exposing a ton of mutators, all of which synchronize on an object you can't sync on. That means you risk concurrency issues if the adapter is mutated while filtering is occurring.
As it stands with your code, the ArrayAdapter is linked up with your productsAll list. Any mutations, accessors, methods etc will always reference that list. At first I was surprised your solution worked! Then I realized you aren't using getItem as is the norm. I imagine you are completely neglecting all the other ArrayAdapter methods, else you'd have seen rather strange behavior. If that's the case, ArrayAdapter isn't really doing anything for you and you're loading up this huge class for nothing. Would be trivial to switch it out with BaseAdapter.
In fact I'm surprised you aren't seeing other strange problems. For instance, no matter what your filtered list shows, your adapter is always registering the productsAll list count instead of the productsShown count. Which may be why you have all these index out of bounds checks? Typically not needed.
I'm also surprised your filtering operation updates the list since you fail to invoke notifyDataSetChanged when finished.
Next big problem, you should never nest adapters. I'm usually advocating this because people embed ListViews...which is another no no of itself. This is the first I've heard about nesting with AutoCompleteTextView though. Slightly different situation, yet I'd still say this is a bad idea. Why? There's no guarantee how many times getView will be invoked for a given position. It could call it once...it could call it 4 times...or more. So imagine recreating your adapter 4 times per item. Even if only 10 items display at a time, you're looking at 40 instantiations of your custom adapter! I sure hope you figured out a way to recycle those adapters to lower that number.
However considering you aren't using the ViewHolder I'm assuming you don't even know about the recycling behavior? ViewHolder is a must do for any adapter. It single handily will provide an enormous performance boast. Right now, you are creating a new view with every getView invocation and ignoring any of the recycled views provided. There a million examples online that show and explain the ViewHolder. Here's one such link.
Side note, ArrayAdapter already implements Filterable. Re-adding the implements in your custom adapter is not needed.
To sum up:
Implement BaseAdapter instead
Don't embed adapters. Find a different way to display your UI without requiring multiple AutoCompleteTextViews inside of a ListView.
Can't really improve your filtering logic without some heavy data pre-processing.
Use ViewHolder paradigm.
Must watch video from Google I/O about ListViews and adapters.
Here's some further readings about the ArrayAdapter.
Related
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.)
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);
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
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;
}
}
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.
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.
I am displaying some data in ListView using my custom ArrayAdapter class. I have a spinner in Action Bar and based on it's item selection I want to filter the data and repopulate it in the list view.
Currently when my activity loads the data shows up correctly, but when I select any item from the spinner, the ListView gets empty and does not show any data, although the Actionbar still appears.
Could you please tell me what I am doing wrong?
This is my custom ArrayAdapter Class :
public class MyAdapter extends ArrayAdapter implements Filterable {
private ArrayList < ArrayList < String >> arrayList;
private LayoutInflater theInflater;
public MyAdapter(Context context, ArrayList arrayList) {
super(context, R.layout.row_layout, arrayList);
this.arrayList = arrayList;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
theInflater = LayoutInflater.from(getContext());
View theView = theInflater.inflate(R.layout.row_layout, parent, false);
ArrayList < String > values = (ArrayList < String > ) getItem(position);
TextView a= (TextView) theView.findViewById(R.id.a);
TextView b= (TextView) theView.findViewById(R.id.b);
TextView c= (TextView) theView.findViewById(R.id.c);
a.setText(values.get(0));
b.setText(values.get(1));
c.setText(values.get(2));
return theView;
}
public Filter geFilter() {
Filter filter = new Filter() {#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
ArrayList al = new ArrayList();
al.add("ValueA1");
al.add("ValueA2");
al.add("ValueA3");
ArrayList bl = new ArrayList();
bl.add("ValueB1");
bl.add("ValueB2");
bl.add("ValueB3");
ArrayList < ArrayList < String >> array = new ArrayList < > ();
array.add(al);
array.add(bl);
results.count = array.size();
results.values = array;
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
arrayList = (ArrayList) results.values;
notifyDataSetChanged();
}
};
return filter;
}
}
In Spinner's item selected listener, I have :
theAdapter.getFilter().filter("Incoming");
Unfortunately, there's a lot you are doing wrong with the adapter. Such as not implementing the ViewHolder paradigm. Note, this is not why you are seeing issues. However it does solve performance issues your solution will create.
The filtering itself is not working because ArrayAdapter tracks it's own internal list of data. The ArrayAdapter makes no guarantees that the list you are tracking externally is the same one. This is especially true during a filter operation. You can read more about the dangers of tracking an external list like you are here.
Your Filter is very problematic.
It re-assigns your external list of data to a new list, which means your arrayList is no longer the same one the internal ArrayAdapter is tracking. This will cause issues later on when you mutate the adapter.
As it stands, your implementation doesn't seem to actually filter anything. Usually you'll also have a second ArrayList in order to track the original data before the filtering starts. Otherwise there's no way to restore the data that was filtered out.
performFiltering() takes place on a background thread. While you don't seem to access arrayList during this method (which would be the only way to actually filter the data), you need to ensure all list modifications are synchronized. ArrayAdapter itself already handles synchronization but there's no way to sync your external arrayList with it. Meaning you can't actually write a filter without risking concurrent modification errors.
In publishResults() when results.count == 0, you must notifyDataSetInvalidated(). If results.count > 0, you must notifyDataSetChanged().
ArrayAdapter already implements Filterable. It's redundant to add that to your custom adapter.
There are a few bugs with the ArrayAdapter own filterable implementation. Here's a piece which talks about the problem and how to work around it. The relevance is that it discusses how to create your own filterable logic to fix the problem. The short, you can't create your own custom filter with the ArrayAdapter. You need to create your own adapter from scratch using BaseAdapter instead.
Alternatively, you can use a third party library which provides a very easy means to implement your own custom filtering logic. I highly suggest taking a look at that guy instead. Additionaly, I highly recommend you Google around more on how to properly create a custom adapter. There's a lot to them and a lot you'll need to learn to properly implement one from scratch.
Do the following in publishResults() method:
Clear the list, add the resultant data again and call notifyDataSetInvalidated() again.
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
arrayList = (ArrayList) results.values;
notifyDataSetChanged();
clear();
for(int i = 0, l = arrayList.size(); i < l; i++) {
add(arrayList.get(i));
}
notifyDataSetInvalidated();
}
In an Android app, I have a common setup -- a ListView with an ArrayAdapter. At a certain point I call the adapter's getFilter().filter() method, which very nicely restricts the collection of ListView items displayed on the screen. But what I'd really like to do is obtain, programmatically, a list of those displayed items. The Filter object itself seems to "know" this information, but I can't get at that information because Filter.values() is protected. That is, I'd like to do something like this:
Filter myfilter = adapter.getFilter();
myfilter.filter(text, new Filter.FilterListener() {
#Override
public void onFilterComplete(int count) {
Filter.FilterResults results = myfilter.values(); // Won't compile!
... do something with results ...
}
}
Is there a way to get what I want, short of implementing my own subclass of ArrayAdapter? I'm thinking, if Google has gone to the trouble of giving us a count of the number of items that have passed through the filter (that is, through the Filter.FilterListener.onFilterComplete(int count) method), they would have made the items themselves available...somehow, somewhere?
When an ArrayAdapter is filtered...the problem is getting the undisplayed items. The displayed items is all you'll ever get to access. So once you conduct a filter, you can simply iterate through the adapter to obtain each item. Pseudocode example:
List<Foo> data = new ArrayList<Foo>();
int count = mAdapter.getCount();
for (int index = 0; index < count; ++index) {
data.add(mAdapter.getItem(index));
}
This is the generic way to obtain the list of items you've placed into the adapter. If a filter operation never happened, it'll return you all the items in the adapter. If filtering has happened, it'll only return you the displayed data. There are some oddities when working with filtering and the ArrayAdapter. I'd recommend reading this, for further info on it's bugs and gotchas.
I'm having a strange issue with a custom implementation of Android's ArrayAdapter. To give some background, I'm trying update a ListView's contents while preserving the current scroll position.
I have a service which executes a thread to update data that's displayed in the ListView. That data is stored in an ArrayList and that ArrayList is used to generate some custom ArrayAdapters for the ListView. The adapters are also updated when an item in the ListView is pressed (either adding or removing an item). I used to just create new adapters each time there was any type of change and then set this new adapter to the ListView. This worked, but caused the ListView to scroll to the top each time. Given the nature of my application this was undesirable. The current scrolled position in the ListView must be maintained between updates.
Instead of creating new adapters I began clearing the adapter that I needed to update using the adapter's clear() method, then rebuild the adapter's items by using the adapter's add() method. Both of these methods are being called on the adapter. The adapters are all set to notifyDataOnChange in their constructors so I don't have to manually call notiftyDatasetChanged() each time (although given my issue I've tried calling it manually as well to no avail).
Here's what my custom adapter looks like:
public class RealmAdapter extends ArrayAdapter<Realm>
{
Context c;
public RealmAdapter(Context context, int resource, int textViewResourceId)
{
super(context, resource, textViewResourceId);
setNotifyOnChange(true);
c = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
...
}
...
}
Long story short, here's my issue. When I call clear() on the adapter, the adapter is not being cleared.
Here's a snippet from my onPostExecute in my thread that does updating. I'm being sure to put it here so it's updating on the UI thread. I also have this exact code copied in a private method in my UI activity. This code does not work in either place:
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
As an example, if the above adapter had 3 items in it, calling a getCount() right after the clear() is returning 3 instead of 0. Likewise, if the appState.favorites ArrayList only has 2 items in it, the getCount() after the loop is still returning 3, not 2. Because the adapter is not responding to any of these calls it makes it impossible to update in any fashion. I can post a Logcat later if that will be helpful, but there are no exceptions or anything useful being displayed.
After busting my head for hours, the issue I appear to be having is that the adapter is not responding to calls to any methods that alter it. I've tried passing an empty ArrayList into the adapter's super() call, this does not help. Am I missing something or using the ArrayAdapter incorrectly? I've searched all over and I've already checked a lot of the common problems such as modifying the underlying array and expecting it to update, not calling (or in my casing setting to the adapter) notifyDatasetChanged(), and using an unsupported operation on the underlying collection.
The declaration of the favoriteAdapter is very simple and is contained in my Application class:
public RealmAdapter favoriteAdapter;
Here is the initialization of the favoriteAdapter from above:
if(appState.favoriteAdapter == null) {
appState.favoriteAdapter = new RealmAdapter(c, R.layout.list_item, R.layout.realm_entry, appState.favorites);
}
else {
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
}
The above code is in both my UI thread and the thread that downloads the refreshed data.
Underneath the code above a filter is put in place:
if(appState.favoriteAdapter != null && RealmSelector.realmFilter != null) appState.favoriteAdapter.getFilter().filter(RealmSelector.realmFilter.getText().toString());
Would the filter affect clearing the list? Logic would dictate not...
I had filters being applied to the custom ArrayAdapter. Apparently this interferes with adding and removing items from the adapter itself? I added this code to my method and it is now working:
if(appState.favoriteAdapter != null && realmFilter != null) {
appState.favoriteAdapter.getFilter().filter(realmFilter.getText().toString());
}
I'd love if anyone could explain why this matters. I thought filters were meant to select subsets of items in the adapter. In my testing I was leaving the text box that is used for the filter empty, thus no actual filter text should have been applied. Again, if someone knows what's going on and could explain to me why this fixes the problem I'd love to know.
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.)
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);
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
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;
}
}
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.
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.