I have an AutoCompleteTextView where the user types an address. I want to be able to show suggestions below as he types. To do that I get a list of possible addresses via the Reverse Geocoding API. i want then to show this list of strings (possible addresses) to the user. Just like the google maps app does.
I have added a TextChangedListener to the AutoCompleteTextView. On the onTextChanged() event an AsyncTask is executed where the list of possible address gets updated on the onPostExecute().
autoText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
new GeoTask().execute();
}
});
Attempt 1
This is the list:
static List<String> suggestionList = new ArrayList<String>();
And this is the code for the adapter for the AutoCompleteTextView :
autoText.setThreshold(3);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, suggestionList);
autoText.setAdapter(adapter);
adapter.setNotifyOnChange(true);
With the above code nothing shows up.
Attempt 2
I also tried using an array as an argument for the adapter and every time the list of address gets updated I convert it to the array.
static String[] suggestions;
static List<String> suggestionList = new ArrayList<String>();
Adapter:
autoText.setThreshold(3);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, suggestionList);
autoText.setAdapter(adapter);
adapter.setNotifyOnChange(true);
AsyncTask:
protected void onPostExecute(Void aVoid) {
...
suggestions = suggestionList.toArray(new String[suggestionList.size()]);
super.onPostExecute(aVoid);
}
How can i make it work? Is there a way to use the adapter with the list?
The reason why nothing shows up is because the list is empty and in onPostExecute of your AsyncTask your only assigning new array to your suggestions reference, what you should really do is use adapters methods to add and remove elements. You can try this code:
adapter.clear();
adapter.addAll(/*new collection of suggestions*/);
in your onPostExecute method.
NOTE: adapter.addAll() method appeared only from 11th API, so if you use lower, then you would have to add each item manually.
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 have an AlertDialog with a ListView set to multiple selection on it. It also has a Button on it.
The Button open another AlertDialog that if ok'ed will remove the selected items from the data set of the ListView, and then tell the adapter of the list view that the dataset has changed with the notifyDataSetChanged() method.
This all works fine except for one thing. The ListView does not update it's content until I interact with something. Then it updates to the correct data.
This is not a big problem, but I really would like the ListView to appear correct at once, and not just after the focus has changed.
Code:
Button remove = (Button) view.findViewById(R.id.btn_remove_questions_edit_rack);
final Context con = this;
remove.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Builder warnBuild = new Builder(con);
warnBuild.setMessage(R.string.question_deletion_warning);
warnBuild.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which)
{
SparseBooleanArray checked = list.getCheckedItemPositions();
for (String s : keys)
{
int i = keys.indexOf(s);
if (checked.get(i))
{
toRemove.add(map.get(s));
map.remove(s);
}
}
keys.clear();
keys.addAll(map.keySet());
((ArrayAdapter) list.getAdapter()).notifyDataSetChanged();
list.clearChoices(); //This makes sure the selection is cleared, if it isn't, some of the other items (those that now has the index of the selected items) will be selected when the View refreshes.
dialog.dismiss();
}
});
//Negative button here, not relevant.
}
});
Where map and keys are:
final HashMap<String, QualityQuestion> map = new HashMap<>();
//I add items to the map
final ArrayList<String> keys = new ArrayList<>(map.keySet());
And toRemove is where I store the items to be removed from the actual object they are on when the ok button on the original AlertDialog is pressed.
This is how I populate my ListView in the first place:
final ListView list = (ListView) view.findViewById(R.id.list_questions_edit_rack);
list.setAdapter(
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1,
keys));
I have tried things like list.invalidateViews(), list.invalidate and other things I found in questions similar to mine here on SO. But none of that made any difference. I suspect my problem to be different from theirs since my items clearly are updated, it just takes a change of focus on the original AlertDialog for the change to be visible.
How can I make the ListView show the changes in it's data source imidiatly insted of after a focus change?
By calling
((ArrayAdapter) list.getAdapter()).notifyDataSetChanged();
you get a fresh adapter which is almost certainly not identical to the anonymous adapter you used to populate your list in the first instance.
See also the documentation for ListView.getAdapter()
Returns the adapter currently in use in this ListView.
The returned adapter might not be the same adapter passed to setAdapter(ListAdapter) but might be a WrapperListAdapter.
From the point of view of this fresh adapter, the data set hasn't changed because the changes happened way before it was instantiated.
To solve your problem, make your list and your list adapter members of your activity class (or the scope where you want to keep them alive):
private ArrayList<String> keys;
private ArrayAdapter myAdapter;
private ListView list;
Then in your "onCreate()"
keys = ...; // initialization of ArrayList with the needed data
myAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1,
keys);
list = (ListView) view.findViewById(R.id.list_questions_edit_rack);
list.setAdapter(myAdapter);
This way, in your "OnClickListener" you can notify "myAdapter":
keys.addAll(map.keySet());
myAdapter.notifyDataSetChanged();
Hope this helps :)
You can tweak it, by granting focus to another view, and then requesting it back:
view.requestFocus();
You can also use:
view.requestFocusFromTouch();
I have a listview that shows the contents of an arraylist. I'm using a simple adaptor to make this possible like so.
public static ArrayList<String> homeScreenContacts = new ArrayList<String>();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.home_screen_contacts_view, NewContact.homeScreenContacts);
The second line is giving me a null pointer exception. I thought about it and I decided it was because the arrayList is empty. So I added the following line between the arraylist declaration and the arrayadaptor declaration...
NewContact.homeScreenContacts.add("A Contact");
This solved the problem and my code worked fine but Now the list view shows "A Contact" and I dont want it to. Is there anyway to get rid of the null pointer exception problem but still have the arraylist empty? Because I want to populate it with user made contacts, not hard-coded, random strings. Thank you.
EDIT: Sorry, The arraylist is located in another class called NewContact, also, I am very beginner Android Programmer I just started.
Simple solution just don't initialize the ListView if there is no element in the ArrayList or the ArrayList is null.
if(NewContact.homeScreenContacts != null && NewContact.homeScreenContacts.size() > 0){
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.home_screen_contacts_view, NewContact.homeScreenContacts);
listView.setAdapter(adapter);
}
Also you need to remember that if you you haven't initialize Adapter then dont initialize the ListView and before any operation on list view you should check is it null or not.
As you have said that you want to populate when user add some contact in the application then on add event only you need to populate or update the ListAdapter.
Hope this solution will resolve your problem.
Try this code
public class YourActivity extends Activity
{
private ListView lv;
public void onCreate(Bundle saveInstanceState) {
setContentView(R.layout.your_layout);
lv = (ListView) findViewById(R.id.your_list_view_id);
// Instanciating an array list (you don't need to do this, you already have yours)
ArrayList<String> your_array_list = new ArrayList<String>();
your_array_list.add("foo");
your_array_list.add("bar");
// This is the array adapter, it takes the context of the activity as a first // parameter, the type of list view as a second parameter and your array as a third parameter
ArrayAdapter<String> arrayAdapter =
new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, your_array_list);
lv.setAdapter(arrayAdapter);
}
}
Works fine for me:
if(arrayList.isEmpty())
{
listView.setAdapter(null);
}
else
{
listAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arrayList);
listView.setAdapter(listAdapter);
}
In one of my activity I have EditText, Submit Button and a ListView. The data of ListView are retrived from database. To retrive data from database and adapt to ListView I used the following code.
private void loadList() {
mylist = new ArrayList<HashMap<String, Object>>();
mylist.clear();
List<Data> catDesc = dbhelper.getCatMasterDesc(id);
ArrayAdapter<Data> adapter = new SimpleAdapter(getApplicationContext(), R.layout.list, catDesc);
lv1 = (ListView) findViewById(R.id.masListView1);
lv1.setAdapter(adapter);
}
And i call this method every time whenever I update the database to show updated listview.
String str = category.getText().toString();
yes.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
dbhelper.UpdateMasterDesc(str);
loadList(); // see here.
}
});
This is the way I am updating my ListView. I think this is not a good way. If yes means please suggest me how can I update my ListView Whenever I update the database.
Thank You.
notifyDataSetchanged() is the answer as proposed by Raghunandan.
But before calling it, you need to update your object data which has been included in your list of catDesc.
Judging from your code I believe what
dbhelper.UpdateMasterDesc(str);
does is adding a new category? If yes, do a
catDesc.add(new Data(whatever you need to declare it with str))
before calling notifyDataSetChanged().
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.