Writing Your Own AutoCompleteTextView
Hey everyone,
Haven’t posted in a while but thought I’d write an example about AutoCompleteTextViews and how to implement a custom one. Much of the code is very similar in nature to that of the custom CursorAdapter which I talked about in this post:
https://thinkandroid.wordpress.com/2010/01/11/custom-cursoradapters/
However, the main difference in this example is the fact that we are going to implement the Filterable class as this is what’s going to dictate what suggestions show up during the autocomplete process. As for the layout of each entry, I implemented this instance using JAVA (hence doing it programmatically) so this example does as an example of how you can write layouts using JAVA in lieu of XML. And so, the code snippet for the adapter itself is below:
public class ContactsAutoCompleteCursorAdapter extends CursorAdapter implements Filterable { private TextView mName, mNumber; public AutoCompleteCursorAdapter(Context context, Cursor c) { super(context, c); mContent = context.getContentResolver(); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final LinearLayout ret = new LinearLayout(context); final LayoutInflater inflater = LayoutInflater.from(context); mName = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); mNumber = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); ret.setOrientation(LinearLayout.VERTICAL); LinearLayout horizontal = new LinearLayout(context); horizontal.setOrientation(LinearLayout.HORIZONTAL); // you can even add images to each entry of your autocomplete fields // this example does it programmatically using JAVA, but the XML analog is very similar ImageView icon = new ImageView(context); int nameIdx = cursor.getColumnIndexOrThrow(People.NAME); int numberIdx = cursor.getColumnIndex(People.NUMBER); String name = cursor.getString(nameIdx); String number = cursor.getString(numberIdx); mName.setText(name); mNumber.setText(number); // setting the type specifics using JAVA mNumber.setTextSize(16); mNumber.setTextColor(Color.GRAY); /** * Always add an icon, even if it is null. Keep the layout children * indices consistent. */ Drawable image_icon = null; if (carrier != CarrierInfo.NOT_CACHED && carrier != CarrierInfo.ERROR && carrier != CarrierInfo.NETWORK_ERROR) { image_icon = context.getResources().getDrawable(R.drawable.icon); } icon.setImageDrawable(image_icon); icon.setPadding(0, -2, 2, 6); // an example of how you can arrange your layouts programmatically // place the number and icon next to each other horizontal .addView(mNumber, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); horizontal.addView(icon, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); ret.addView(mName, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); ret.addView(horizontal, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); return ret; } @Override public void bindView(View view, Context context, Cursor cursor) { int nameIdx = cursor.getColumnIndexOrThrow(People.NAME); int numberIdx = cursor.getColumnIndex(People.NUMBER); String name = cursor.getString(nameIdx); String number = cursor.getString(numberIdx);); /** * Always add an icon, even if it is null. Keep the layout children * indices consistent. */ Drawable image_icon = null; if (carrier != CarrierInfo.NOT_CACHED && carrier != CarrierInfo.ERROR && carrier != CarrierInfo.NETWORK_ERROR) { image_icon = context.getResources().getDrawable(R.drawable.icon); } // notice views have already been inflated and layout has already been set so all you need to do is set the data ((TextView) ((LinearLayout) view).getChildAt(0)).setText(name); LinearLayout horizontal = (LinearLayout) ((LinearLayout) view).getChildAt(1); ((TextView) horizontal.getChildAt(0)).setText(number); ((ImageView) horizontal.getChildAt(1)).setImageDrawable(image_icon); } @Override public String convertToString(Cursor cursor) { // this method dictates what is shown when the user clicks each entry in your autocomplete list // in my case i want the number data to be shown int numCol = cursor.getColumnIndexOrThrow(People.NUMBER); String number = cursor.getString(numCol); return number; } @Override public Cursor runQueryOnBackgroundThread(CharSequence constraint) { // this is how you query for suggestions // notice it is just a StringBuilder building the WHERE clause of a cursor which is the used to query for results if (getFilterQueryProvider() != null) { return getFilterQueryProvider().runQuery(constraint); } StringBuilder buffer = null; String[] args = null; if (constraint != null) { buffer = new StringBuilder(); buffer.append(People.NAME + " IS NOT NULL AND " + People.NUMBER_KEY + " IS NOT NULL AND "); buffer.append("UPPER("); buffer.append(People.NAME); buffer.append(") GLOB ?"); args = new String[] { constraint.toString().toUpperCase() + "*" }; } return mContent.query(People.CONTENT_URI, Constants.PEOPLE_PROJECTION, buffer == null ? null : buffer .toString(), args, People.DEFAULT_SORT_ORDER); } private ContentResolver mContent; }
And that’s it! So just a brief explanation (in addition to comments I put into the code at each section). Basically it works the same way as a custom CursorAdapter, but instead we implement the Filterable class as this will dynamically query our database for suggestions based off of what the user has typed into the AutoCompleteTextView. Then, we have a method called convertToString and that determines what field of the cursor gets converted to be displayed in your AutoCompleteTextView. I’m not sure what it defaults to typically, but at least in our example we want to specific between whether or not we want the contact’s name to be displayed or the contact’s phone number. When I wrote this AutoCompleteTextView, I wanted to query for the person’s contacts and allow them to quickly call that contact, and so it made more sense to display the phone number as opposed to the name.
With this you have a lot of control over not only what suggestions are displayed, but also what gets converted and how all of your information gets displayed.
So the one question remains – how does one implement this. So here’s a little code snippet on how to implement this example:
public class A extends Activity { @Override public void onCreate (Bundle savedInstanceState) { setContentView(R.layout.main); // R.id.auto_contact_edit is just an AutoCompleteTextView defined in the XML layout R.layout.main AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.auto_contact_edit); Cursor c = getContentResolver() .query(People.CONTENT_URI, Constants.PEOPLE_PROJECTION, People.NAME + " IS NOT NULL AND " + People.NUMBER_KEY + " IS NOT NULL", null, People.DEFAULT_SORT_ORDER); ContactsAutoCompleteCursorAdapter adapter = new ContactsAutoCompleteCursorAdapter(this, c); textView.setAdapter(adapter); // you can also prompt the user with a hint textView.setHint("type name"); // rest of your code } }
Pretty simple.
Hope this was helpful. Happy coding.
– jwei
Oh, just noticed this… don’t have the time to look through it atm as I need to learn some different stuff done in Android atm, but thanks for fulfilling my request ^^ hehe, it will certainly be looked at when I have the time 🙂
Thanks for the example, but I’m wondering how this example might be modified to autocomplete based on either the contact name or the contact email. For example, the native Android email client will autocomplete on either. If you have a friend like Mary Jones , the autocomplete will work when you begin typing either “ma” or “mj”. How would you accomplish this?
Hey Derek,
You’re going to want to look at the runQueryOnBackgroundThread() method. This is where you take the input and customize what results get returned.
– jwei
Could it be, that you have a small error in your code?!
There is ContactsAutoCompleteCursorAdapter class, but AutoCompleteCursorAdapter-Constructor.
Mur
Hi, Thank You for this post,
exactly what i am looking for.
Please could you make this post more simple using XML based layout.
I am very very new to Android and Java, and facing problems while running it..
Thank You again..
Constants.PEOPLE_PROJECTION ???
is this the same PEOPLE_PROJECTION used in the APIDemos?
have you done anything like this for Android 2.x?
thanks,
kp
there is a least 4 compilation errors in this example ….
– “CarrierInfo” does not exists
– “Constants.PEOPLE_PROJECTION” does not exists
– the variable “carrier” is never defined
– and last but not least : the constructor name is wrong ….
Excellent idea, but have you implemented it successfully? The code is incomplete and with plenty syntax errors.
Hey Baychev,
Yea I’ve implemented it successfully. The code snippet is actually from a working implementation that I did for an app I was working on. Then I just took the working class and stripped it of all irrelevant code, generalized some variable names etc and posted it.
That probably explains any syntax errors that you might see.
– jwei
This looks really great – it will help me fix some of the comments I’ve gotten in my App…
Thanks for this — it gave me just enough of the CursorAdapter snippets so that I could implement this on my own.
I’ve compiled this code, and got it working with Android 2.2+. Let me know if someone needs it. Just giving something back to the community.
Thanks and credit go to the original poster.
I implemented a code for this too,I’m facing some problem,after I select 1 item from the autocomplete list I want the list to be shown again when the I starts typing another string
Is it possible with your method?
Hi Rahul,
Yes it should be – I’m not sure what the connection is between selecting an item in the autocomplete list and having it show up again. Shouldn’t the two events be independent of one another? In other words, the autocomplete list should be ignorant of whether or not you’ve previously selected something…
– jwei
Great post! Can this be used to query an external mongodb places database? I am currently trying to implement that in an app.
Thanks!
Forgot “notify via email”, reply to this post instead.
Great post! very good commentry to understand the code.. was very helpful in fixing one of my problem. thanks.!