11

I have an ArrayAdapter (myAdapter) attached to an AutoCompleteTextView (textView) component.
Once the user presses a character I would like to populate AutoCompleteTextView's drop down list with items containing this character.
I retrieve the items using AsyncTask (which uses a web service).

I call myAdapter.add(item) but the drop down list is empty.
I added a call myAdapter.getCount() after each addition and it shows zero every time. Calling notifyDataSetChanged() didn't help.
I even tried to add simple String objects instead of my custom objects, to no avail.
What am I doing wrong?

Edit: I changed the code as miette suggested below but still to no avail.
Generally, what I do is after text is changed in my auto complete text view, I call a new AsyncTask and pass it the entered text and a Handler (see afterTextChanged()). The task retrieves objects relevant to the text and once done the Handler's handleMessage() is called. In handleMessage() I attempt to populate the adapter's objects. But still the adapter's drop down list ends up empty.

Here is my code:

public class AddStockView extends Activity
        implements OnClickListener, OnItemClickListener, TextWatcher {  

    ArrayAdapter<Stock> adapter;
    AutoCompleteTextView textView;
    Vector<Stock> stocks;
    public AddStockView() {
      // TODO Auto-generated constructor stub
      stocks = new Vector<Stock>();
    }

    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      requestWindowFeature(Window.FEATURE_NO_TITLE); 
      setContentView(R.layout.add_stock_view);

      findViewById(R.id.abort_button).setOnClickListener(this);

      adapter = new ArrayAdapter<Stock>(this,
      android.R.layout.simple_dropdown_item_1line, stocks);
      //adapter.setNotifyOnChange(true);
      textView = (AutoCompleteTextView)
      findViewById(R.id.search_edit_text);
      textView.setAdapter(adapter);
      textView.setOnItemClickListener(this);
      textView.addTextChangedListener(this);

    }
    @Override
    public void onClick(View v) {
      // TODO Auto-generated method stub
      switch (v.getId())
      {
        case R.id.abort_button:
        finish();
        break;
        case R.id.search_edit_text:

        break;
      }
    }
    @Override
    public void onItemClick(AdapterView<?> parent, View v,
                            int position, long id) {
      // TODO Auto-generated method stub
      Stock stockToAdd = (Stock)parent.getAdapter().getItem(position);
      //TODO: Add the above stock to user's stocks and close this screen
      finish();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {  
      super.onCreateOptionsMenu(menu);  
      getMenuInflater().inflate(R.layout.menu, menu);  

      CategoryMenu.getInstance().populateMenu(menu);
      return true;  
    }  

    @Override  
    public boolean onOptionsItemSelected(MenuItem item) {  
      CategoryMenu.getInstance().menuItemSelected(item, this);
      return false; 
    }  

    @Override  
    public boolean onPrepareOptionsMenu(Menu menu) { 
      return true;  
    }
    @Override
    public void afterTextChanged(Editable text) {
      // TODO Auto-generated method stub
      if (text.toString().equals(""))
        return;
      new AppTask().execute(new AppTask.Payload(Consts.taskType.SEARCH_STOCK,
                                          new Object[] {text, handler}, this));

    }
    @Override
    public void beforeTextChanged(CharSequence a0, int a1, int a2, int a3) {
      // TODO Auto-generated method stub
    }
    @Override
    public void onTextChanged(CharSequence a0, int a1, int a2, int a3) {
      // TODO Auto-generated method stub
    }
    private void addStockItemsToAdapter(Vector<Object> dataItems)
    {
      for (int i = 0; i <dataItems.size(); i++)
      {
        Stock stk = (Stock)dataItems.elementAt(i);
        stocks.add(stk);
      }
    }

    public void populateAdapter()
    {
      addStockItemsToAdapter(ContentReader.getInstance.getDataItems());    
      adapter.notifyDataSetChanged();
      int size = adapter.getCount(); // size == 0 STILL!!!!
      textView.showDropDown();
    }
    final Handler handler = new Handler() {
      public void handleMessage(Message msg) {
        populateAdapter();
      }
    };
}

Thanks a lot, Rob

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
Rob
  • 15,041
  • 6
  • 25
  • 27

3 Answers3

12

I had the exact same problem. After examining the ArrayAdapter and AutoCompleteTextView source code, I found out that the problem was, in short, that:

  • the original object list is stored in ArrayAdapter.mObjects.
  • However, AutoCompleteTextView enables ArrayAdapter's filtering, meaning that new objects are added to ArrayAdapter.mOriginalValues, while mObjects contains the filtered objects.
  • ArrayAdapter.getCount() always returns the size of mObjects.

My solution was to override ArrayAdapter.getFilter() to return a non-filtering filter. This way mOriginalValues is null and mObjects is used instead in all cases.

Sample code:

public class MyAdapter extends ArrayAdapter<String> {
    NoFilter noFilter;
    /*
    ...
    */

    /**
     * Override ArrayAdapter.getFilter() to return our own filtering.
     */
    public Filter getFilter() {
        if (noFilter == null) {
            noFilter = new NoFilter();
        }
        return noFilter;
    }

    /**
     * Class which does not perform any filtering.
     * Filtering is already done by the web service when asking for the list,
     * so there is no need to do any more as well.
     * This way, ArrayAdapter.mOriginalValues is not used when calling e.g.
     * ArrayAdapter.add(), but instead ArrayAdapter.mObjects is updated directly
     * and methods like getCount() return the expected result.
     */
    private class NoFilter extends Filter {
        protected FilterResults performFiltering(CharSequence prefix) {
            return new FilterResults();
        }

        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {
            // Do nothing
        }
    }
}
Giorgos Kylafas
  • 2,243
  • 25
  • 25
  • in other cases the filtering is what you really need and you can use it as explained [here](http://android.foxykeep.com/dev/how-to-add-autocompletion-to-an-edittext) in the Webservice example. – vfede Feb 19 '15 at 11:57
2

Create an array adapter with a vector or array like:

ArrayAdapter(Context context, int textViewResourceId, T[] objects)

By initializing your arrayadapter, you will make it listen to objects array. Do not add item to the adapter or clear the adapter, do your additions in "objects" array and also clear it. After changes on this array call

adapter.notifyDataSetChanged();

More specifically

ArrayAdapter<YourContentType> yourAdapter = new ArrayAdapter<YourContentType> (this,R.id.OneOfYourTextViews,YourDataList);

yourAdapter.notifyDataSetChanged();    
aTextView.setText(yourAdapter.isEmpty() ? "List is empty" : "I have too many objects:)");

This should be done after loading YourDataList, I checked your code, are you sure handler calls addStockItemsToAdapter() before you look your adapter is empty or not? You should also check if stocks vector has any elements in it.

assylias
  • 321,522
  • 82
  • 660
  • 783
miette
  • 1,911
  • 1
  • 11
  • 12
  • I did as you suggested but it still doesn't work. I have a Handler whose handleMessage() is called once the data structure arrives. In handleMessage() I call a function which iterates thru the data structure and adds elements to the adapter's T[] objects and then calls adapter.notifyDataSetChanged(). After all this adapter.getCount() still returns zero. What the hell could it be? – Rob Mar 02 '10 at 13:28
  • Hi again, i am changing my answer for you. I have tested what you want to do and it works for me. – miette Mar 03 '10 at 07:39
  • Hi miette, I appreciate you looking into this. You are suggesting the same as Warren and indeed this solved the problem. Btw, I realize that calling notifyDataSetChanged() is redundant now since the "new ArrayAdapter" statement contains the updated data source. I thought I could have my data source (YourDataList) empty upon creation of the adapter, add items to it along the way and call notifyDataSetChanged() when done adding, but it seems that it doesn't work, does it? Anyway, much obliged my man. – Rob Mar 03 '10 at 11:13
  • Actually adding items along the way must have been working. To see this action I can suggest that call YourDataList.clear() and do some additions to YourDataList manually, for example when clicking a dummy button then call notifyDataSetChanged(), it must work in this way. – miette Mar 03 '10 at 11:40
0

Where do you call addItemsToAdapter()? Can you show us, how you have tried to add simple Strings to your Adapter?

Edit: out of the comments the helpful code sample:

adapter = new ArrayAdapter<Stock>(this, android.R.layout.simple_dropdown_item_1line, stocks);
adapter.notifyDataSetChanged();
textView.setAdapter(adapter);
WarrenFaith
  • 57,492
  • 25
  • 134
  • 150
  • Here's what I did: I defined a Handler and put it in my AsyncTask (called AppTask)params like so: new AppTask().execute(new AppTask.Payload(Consts.taskType.SEARCH_STOCK, new Object[] {text, handler}, this)); Then, in my Handler's handleMessage() I called addItemsToAdapter. As for the Strings, I declared this array: private static final String[] COUNTRIES = new String[] { "Belgium", "France", "Italy", "Germany", "Spain" }; and instead of calling: myAdapter.add(tmpItem); I called; myAdapter.add(COUNTRIES[i]) and even COUNTRIES[0]. Thanks for your help! – Rob Mar 01 '10 at 14:13
  • You can upload a minimalistic project zip somewhere or edit your question above and add the code there... – WarrenFaith Mar 01 '10 at 14:44
  • Hm... I have only one guess: ContentReader.getInstance.getDataItems() is empty... Have you tried to use some Log statements to see some values or better, have you tried to debug your application? – WarrenFaith Mar 02 '10 at 14:01
  • The data items are there. During debug I can see that the data items (stock info in this case) structure is populated with the relevant items. I also see each stock being added to Vector stocks... – Rob Mar 02 '10 at 14:08
  • 1
    hm... the last time I had such an issue with a spinner, I recreated the adapter with the new data (in your case the new stocks): adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, stocks); adapter.notifyDataSetChanged(); textView.setAdapter(adapter); that helped me... – WarrenFaith Mar 02 '10 at 14:38
  • That did it! I really appreciate your help, Warren. But now I can't help but wonder - what's the purpose of adding (either to the adapter itself or its objects) if it cannot be done after binding the adapter... I can simply create the data structure beforehand and provide it as the last argument in the new ArrayAdapter statement... – Rob Mar 02 '10 at 15:29