Android AutocompleteTextView with Custom Adapter Example


Before this Android AutocompleteTextView with custom adapter example, we made the code on Android AutocompleteTextView with suggestions from SQLite database, that’s really fine and useful, but here’s another problem:

What if you want to customize the appearance of the drop-down suggestions?

This is where the custom ArrayAdapter will come to the scene. Using this post, you will be able to customize the look and feel of your AutocompleteTextView drop down – according to your app design theme or visual needs.

We will cover the following content:

1.0 AutocompleteTextView with Custom Adapter Video Demo
2.0 Android AutoComplete EditText Program Files
3.0 Android AutocompleteTextView with Custom Adapter Example Codes
3.1 to 3.8 Main Files Description with Codes Explained via Comments

DOWNLOAD SOURCE CODE

1.0 AutocompleteTextView with Custom Adapter Video Demo

Here’s a video I shoot so you can see the final output of our code for today.

2.0 Android AutoComplete EditText Program Files

You may click on each of the files below to see the code and some insights or explanations. File # 1 and 2 were found in your res/layout/ folder, the rest is of course in the src/your.package.name

  1. activity_main.xml
  2. list_view_row.xml
  3. MainActivity.java
  4. AutocompleteCustomArrayAdapter.java
  5. CustomAutoCompleteView.java
  6. DatabaseHandler.java
  7. CustomAutoCompleteTextChangedListener.java
  8. MyObject.java

3.0 Android AutocompleteTextView with Custom Adapter Example Codes

Now here’s the exciting part. We’ll dig deeper with each main files of the program, see the codes and some explanation. Enjoy!

3.1 activity_main.xml – see how our custom AutoCompleteTextView were put in the XML layout.

The tag looks like com.example.autocompletetextviewdb.CustomAutoCompleteView because it was referenced to our CustomAutoComputeView.java file.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="" />

    <com.example.autocompletetextviewcustomadapter.CustomAutoCompleteView
        android:id="@+id/myautocomplete"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:completionThreshold="1" >
    </com.example.autocompletetextviewcustomadapter.CustomAutoCompleteView>

</LinearLayout>

3.2 list_view_row.xml – helps customize the appearance of AutoCompleteTextView suggestions, you can add an ImageView or something suits your app theme or design.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp" >

    <TextView
        android:id="@+id/textViewItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:text="Item name here..."
        android:textSize="15dp" />

</RelativeLayout>

3.3 MainActivity.java – where the program will try to insert sample data, initialize some variables and will be shown when you the program runs.

package com.example.autocompletetextviewcustomadapter;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

    /*
     * Change to type CustomAutoCompleteView instead of AutoCompleteTextView 
     * since we are extending to customize the view and disable filter
     * The same with the XML view, type will be CustomAutoCompleteView
     */
    CustomAutoCompleteView myAutoComplete;
    
    // adapter for auto-complete
    ArrayAdapter<MyObject> myAdapter;
    
    // for database operations
    DatabaseHandler databaseH;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        try{
            
            // instantiate database handler
            databaseH = new DatabaseHandler(MainActivity.this);
            
            // put sample data to database
            insertSampleData();
            
            // autocompletetextview is in activity_main.xml
            myAutoComplete = (CustomAutoCompleteView) findViewById(R.id.myautocomplete);
            
            myAutoComplete.setOnItemClickListener(new OnItemClickListener() {

                @Override
                public void onItemClick(AdapterView<?> parent, View arg1, int pos, long id) {
                    
                    RelativeLayout rl = (RelativeLayout) arg1;
                    TextView tv = (TextView) rl.getChildAt(0);
                    myAutoComplete.setText(tv.getText().toString());
                    
                }

            });
            
            // add the listener so it will tries to suggest while the user types
            myAutoComplete.addTextChangedListener(new CustomAutoCompleteTextChangedListener(this));
            
            // ObjectItemData has no value at first
            MyObject[] ObjectItemData = new MyObject[0];
            
            // set the custom ArrayAdapter
            myAdapter = new AutocompleteCustomArrayAdapter(this, R.layout.list_view_row_item, ObjectItemData);
            myAutoComplete.setAdapter(myAdapter);
        
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void insertSampleData(){
        
        // CREATE
        databaseH.create( new MyObject("January") );
        databaseH.create( new MyObject("February") ); 
        databaseH.create( new MyObject("March") );
        databaseH.create( new MyObject("April") );
        databaseH.create( new MyObject("May") );
        databaseH.create( new MyObject("June") );
        databaseH.create( new MyObject("July") );
        databaseH.create( new MyObject("August") );
        databaseH.create( new MyObject("September") );
        databaseH.create( new MyObject("October") );
        databaseH.create( new MyObject("November") );
        databaseH.create( new MyObject("December") );
        databaseH.create( new MyObject("New Caledonia this is just to make and see if the text will go down") ); 
        databaseH.create( new MyObject("New Zealand this is just to make and see if the text will go down") );
        databaseH.create( new MyObject("Papua New Guinea this is just to make and see if the text will go down") );
        databaseH.create( new MyObject("COFFEE-1K") );
        databaseH.create( new MyObject("coffee raw") );
        databaseH.create( new MyObject("authentic COFFEE") );
        databaseH.create( new MyObject("k12-coffee") );
        databaseH.create( new MyObject("view coffee") );
        databaseH.create( new MyObject("Indian-coffee-two") );
        
    }
}

3.4 AutocompleteCustomArrayAdapter.java – where you can customize the appearance of the suggestion drop-down. If your suggestions will contain a large list, I suggest using a ViewHolder pattern to make it smooth.

package com.example.autocompletetextviewcustomadapter;

import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class AutocompleteCustomArrayAdapter extends ArrayAdapter<MyObject> {

    final String TAG = "AutocompleteCustomArrayAdapter.java";
        
    Context mContext;
    int layoutResourceId;
    MyObject data[] = null;

    public AutocompleteCustomArrayAdapter(Context mContext, int layoutResourceId, MyObject[] data) {

        super(mContext, layoutResourceId, data);
        
        this.layoutResourceId = layoutResourceId;
        this.mContext = mContext;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        
        try{
            
            /*
             * The convertView argument is essentially a "ScrapView" as described is Lucas post 
             * http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
             * It will have a non-null value when ListView is asking you recycle the row layout. 
             * So, when convertView is not null, you should simply update its contents instead of inflating a new row layout.
             */
            if(convertView==null){
                // inflate the layout
                LayoutInflater inflater = ((MainActivity) mContext).getLayoutInflater();
                convertView = inflater.inflate(layoutResourceId, parent, false);
            }
            
            // object item based on the position
            MyObject objectItem = data[position];

            // get the TextView and then set the text (item name) and tag (item ID) values
            TextView textViewItem = (TextView) convertView.findViewById(R.id.textViewItem);
            textViewItem.setText(objectItem.objectName);
            
            // in case you want to add some style, you can do something like:
            textViewItem.setBackgroundColor(Color.CYAN);
            
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return convertView;
        
    }
}

3.5 CustomAutoCompleteView.java – since we have to display everything the database gets, we have to display the AutoCompleteTextView’s default filtering function. This is also what was called in the XML layout. See activity_main.xml code above.

package com.example.autocompletetextviewcustomadapter;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.AutoCompleteTextView;

public class CustomAutoCompleteView extends AutoCompleteTextView {

    public CustomAutoCompleteView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
    
    public CustomAutoCompleteView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public CustomAutoCompleteView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    // this is how to disable AutoCompleteTextView filter
    @Override
    protected void performFiltering(final CharSequence text, final int keyCode) {
        String filterText = "";
        super.performFiltering(filterText, keyCode);
    }
}

3.6 DatabaseHandler.java – where we insert sample data and query the database for autocomplete suggestions.

package com.example.autocompletetextviewcustomadapter;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DatabaseHandler extends SQLiteOpenHelper {

    // for our logs
    public static final String TAG = "DatabaseHandler.java";

    // database version
    private static final int DATABASE_VERSION = 5;

    // database name
    protected static final String DATABASE_NAME = "NinjaDatabase2";

    // table details
    public String tableName = "locations";
    public String fieldObjectId = "id";
    public String fieldObjectName = "name";

    // constructor
    public DatabaseHandler(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    // creating table
    @Override
    public void onCreate(SQLiteDatabase db) {

        String sql = "";

        sql += "CREATE TABLE " + tableName;
        sql += " ( ";
        sql += fieldObjectId + " INTEGER PRIMARY KEY AUTOINCREMENT, ";
        sql += fieldObjectName + " TEXT ";
        sql += " ) ";

        db.execSQL(sql);

    }

    
    // When upgrading the database, it will drop the current table and recreate.
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        String sql = "DROP TABLE IF EXISTS " + tableName;
        db.execSQL(sql);

        onCreate(db);
    }

    
    // create new record
    // @param myObj contains details to be added as single row.
    public boolean create(MyObject myObj) {

        boolean createSuccessful = false;

        if(!checkIfExists(myObj.objectName)){
                    
            SQLiteDatabase db = this.getWritableDatabase();
            
            ContentValues values = new ContentValues();
            values.put(fieldObjectName, myObj.objectName);
            createSuccessful = db.insert(tableName, null, values) > 0;
            
            db.close();
            
            if(createSuccessful){ 
                Log.e(TAG, myObj.objectName + " created.");
            }
        }
        
        return createSuccessful;
    }
    
    // check if a record exists so it won't insert the next time you run this code
    public boolean checkIfExists(String objectName){
        
        boolean recordExists = false;
                
        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery("SELECT " + fieldObjectId + " FROM " + tableName + " WHERE " + fieldObjectName + " = '" + objectName + "'", null);
        
        if(cursor!=null) {
            
            if(cursor.getCount()>0) {
                recordExists = true;
            }
        }

        cursor.close();
        db.close();
        
        return recordExists;
    }

    // Read records related to the search term
    public MyObject[] read(String searchTerm) {

        // select query
        String sql = "";
        sql += "SELECT * FROM " + tableName;
        sql += " WHERE " + fieldObjectName + " LIKE '%" + searchTerm + "%'";
        sql += " ORDER BY " + fieldObjectId + " DESC";
        sql += " LIMIT 0,5";

        SQLiteDatabase db = this.getWritableDatabase();

        // execute the query
        Cursor cursor = db.rawQuery(sql, null);

        int recCount = cursor.getCount();
        
        MyObject[] ObjectItemData = new MyObject[recCount];
        int x = 0;
        
        // looping through all rows and adding to list
        if (cursor.moveToFirst()) {
            do {

                String objectName = cursor.getString(cursor.getColumnIndex(fieldObjectName));
                Log.e(TAG, "objectName: " + objectName);
                
                MyObject myObject = new MyObject(objectName);

                ObjectItemData[x] = myObject;
                
                x++;
                
            } while (cursor.moveToNext());
        }

        cursor.close();
        db.close();

        return ObjectItemData;
        
    }

}

3.7 CustomAutoCompleteTextChangedListener.java – each time the user types a character, it queries the database and updates our customized ArrayAdapter.

package com.example.autocompletetextviewcustomadapter;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;

public class CustomAutoCompleteTextChangedListener implements TextWatcher{

    public static final String TAG = "CustomAutoCompleteTextChangedListener.java";
    Context context;
    
    public CustomAutoCompleteTextChangedListener(Context context){
        this.context = context;
    }
    
    @Override
    public void afterTextChanged(Editable s) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void onTextChanged(CharSequence userInput, int start, int before, int count) {

        try{
            
            // if you want to see in the logcat what the user types
            Log.e(TAG, "User input: " + userInput);
    
            MainActivity mainActivity = ((MainActivity) context);
            
            // update the adapater
            mainActivity.myAdapter.notifyDataSetChanged();
            
            // get suggestions from the database
            MyObject[] myObjs = mainActivity.databaseH.read(userInput.toString());
            
            // update the adapter
            mainActivity.myAdapter = new AutocompleteCustomArrayAdapter(mainActivity, R.layout.list_view_row_item, myObjs);
            
            mainActivity.myAutoComplete.setAdapter(mainActivity.myAdapter);
            
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    

}

3.8 MyObject.java – used for the sample data to appear in the auto-complete suggestion.

package com.example.autocompletetextviewcustomadapter;

public class MyObject {

    public String objectName;

    // constructor for adding sample data
    public MyObject(String objectName){
        
        this.objectName = objectName;
    }

}

I recommend reading more about Android AutocompleteTextView. Feel free to drop your questions or suggestions to improve this Android AutocompleteTextView with custom adapter example. Thanks!


21 responses to “Android AutocompleteTextView with Custom Adapter Example”

  1. CustomAutoCompleteView – makes no sense. you can use native AutocompleteTextView for your purposes in appropriate example. and why are you calling “notifyDataSetChanged” before setting an adapter?

    • Hi @disqus_zJ00KJMjhq:disqus, thanks for taking the time to comment and try to improve this post, I appreciate it.

      As stated above, I used CustomAutoCompleteView (extends AutoCompleteTextView) to disable the default filtering mechanism of AutoCompleteTextView.

      This is based on my testing. For instance, I have 5 records in the database:

      1. Cat
      2. Dog
      3. Snake
      4. Dragon
      5. Horse

      If we did NOT use CustomAutoCompleteView and typed “a”, nothing will be returned and nothing will be seen on the dropdown.

      If we use CustomAutoCompleteView and typed “a”, records “Cat”, “Snake” and “Dragon” will be returned for the autocomplete dropdown – since each of those words have the letter “a”.

      If you have a better solution, I’m more than willing to update this post.

      Regarding the “notifyDataSetChanged”, I’m calling it before setting the adapter to let AutoCompleteTextView know that the dropdown items will be changed. If I remember it correctly, I tried to call it after setting the adapter but an error was returned.

      • Hey,
        I also had exact same doubts. In my opinion calling “notifyDataSetChanged” doesn’t really matter since you are creating a new AutocompleteCustomArrayAdapter in the next line, so that line can be removed. I also believe creating a new adapter everytime is not a correct way to do it. I tried debugging with it and adapter’s getView method was called several times for a single text change, so something isn’t right about it. I tried making a function inside adapter to change data to new supplied data(similar to what we do in Cursor Adapters) but its not working somehow.

  2. Hi!
    Thanks for this great tuto.
    Little advice for your Adapter, in the getView use this ” (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)” instead of your “LayoutInflater inflater = ((MainActivity) mContext).getLayoutInflater();”

    If your really want your adapter to be scalable :)

  3. for (ConversationsMember customer : memberListAll)

    {
    if(customer.getName().toLowerCase().startsWith(constraint.toString().toLowerCase())){
    suggestions.add(customer);
    }

    This is the code from default filter..To populate suggestions..If you want you can modify this with this..

    for (ConversationsMember customer : memberListAll)
    {
    if(customer.getName().toLowerCase().contains(constraint.toString().toLowerCase())){
    suggestions.add(customer);
    }

    So,It will return data like,Cat,Snake,Dragon..If you type only ‘a’..not necessarily to be start with that letter.

  4. hello

    thanks for the good post .
    your post targeting how to customize the appearance of the drop-down suggestions

    but

    1- can you tell me please how can I change the suggestion not to be appeared – as drop-down but appear as buttons displayed horizontally instead of certically

    2- can I use 2 lists of different suggestion to drive the same autocomplete ?

  5. hi , i have a auto complete to search a Synonyme the list item .. like syno of “January” is “ashu”. Now i will search the ashu but show the “january ” in the popup list . any sujection .

  6. nice artitcle, it helped me, thank you.
    But I found that you could not to create CustomAutoCompleteTextChangedListener and CustomAutoCompleteView. And creating adapter each time user types letter is too expensive, so maybe someone it will help, all you have to do is override getFilter:

    public class SearchAdapter extends ArrayAdapter

    {
    //SearchData class contains only 2 fields id and name
    private List mResultList;
    private int mResource;

    public SearchAdapter (Context context, int resource)
    {
    super(context, resource);
    mResource = resource;
    mResultList = new ArrayList();
    }

    @Override
    public View getView (int position, View convertView, ViewGroup parent)
    {
    if (convertView == null)
    {
    convertView = LayoutInflater.from(parent.getContext()).inflate(mResource, parent, false);
    }
    TextView tv = (TextView) convertView.findViewById(R.id.tv_search);
    tv.setText(mResultList.get(position).animalName);
    return convertView;
    }

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

    @Override
    public SearchData getItem (int position)
    {
    return mResultList.get(position);
    }

    @Override
    public Filter getFilter ()
    {
    return new Filter()
    {
    @Override
    protected FilterResults performFiltering (CharSequence constraint)
    {
    FilterResults fr = new FilterResults();
    if (constraint != null)
    {
    //here I make search from db and return result list, just replace with your own logic
    List results = DBHelper.getInstance().search(constraint);
    fr.count = results.size();
    fr.values = results;
    }
    return fr;
    }

    @Override
    protected void publishResults (CharSequence constraint, FilterResults results)
    {
    if (results != null && results.count > 0)
    {
    mResultList = (List) results.values;
    notifyDataSetChanged();
    } else
    notifyDataSetInvalidated();
    }
    };
    }
    }

    • Hey Михаил Грембовский,
      Thanks for sharing this code. I tried it but somehow value of ‘constraint’ in getFilter() is always coming “” for me. Any suggestions?

  7. Hello… everyone…I must say this tutorial did gave me hope for my college project. I just need a little bit more help. i am designing an Android Soft keyboard which do autocorrect and autocomplete on basis of user input.For now i need word suggestions in my keyboard. Can you help me in how to embed this code in my project as here you have used id of EditText but in my case, EditText can be anywhere as whole keyboard can be used anywhere in the mobile

  8. Optionally in CustomAutoCompleteView.java,

    You can override the replaceText function of its super class.

    This would enable to customize the text to be set in the view after the click event

  9. this code is really helpful. but we can simply set the autoCompleteTextView.setThreshold(100000);
    Then the autocomplete text will do nothing, no filter nothing. Then you can simply call showDropDown() to show the list

  10. MyObject[] myObjs = mainActivity.databaseH.read(userInput.toString()); is generating MyObject[] required. List provided

Leave a Reply

Your email address will not be published. Required fields are marked *