Android Realtime (Instant) Search with Filter Class and Filterable Interface Using Custom and Inbuilt Adapter

Android has an excellent Fitler class that helps us filter data from an entire data set based on some pattern (for example a string) provided. Filters are usually created by classes implementing the Filterable interface. It makes sense to use filters (implement the Filterable interface) mostly with adapters, which means Filterable classes are usually Adapter implementations.

The Filterable interface has just one job which is to return a filter that can be used to constrain data based on some filtering pattern. It has a getFilter() method that must be overridden by the adapter returning a filter. Here’s what I mean in terms of coding:

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

class CustomAdapter extends ArrayAdapter implements Filterable {
    public CustomAdapter(Context context, int resource) {
        super(context, resource);
    }

    @Override
    public Filter getFilter() {
        // return a filter that filters data based on a constraint

        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                return null;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {

            }
        };
    }
}

Don’t sweat what’s going on with implements Filterable and new Filter() { ... }. I’ll explain everything as we proceed gradually.

Implementing a Custom Filter

Let’s see how to create a custom filter by extending the Filter class. Here’s the basic blueprint:

private class ContactsFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        return null;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {

    }
}

The Filter class has 2 abstract methods that must be overridden:

  • performFiltering() – Filter the data based on a pattern in a worker thread. It must return a FilterResults object which holds the results of the filtering operation. A FilterResults object has 2 properties, count that holds the count of results computed by the operation and values that contains the values returned by the same filtering operation.
  • publishResults() – Once the filtering is completed, the results are passed through this method to publish them in the user interface on the UI thread.

The entire filter operation is performed asynchronously.

We’ll now see how to code our performFiltering() and publishResults() to filter our phone’s contacts. Let’s say I’ve a class like this to represent my phonebook’s contacts:

public class Contact {
    public String mName;
    public String mNumber;
    public boolean mIsSeparator;

    public Contact(String name, String number, boolean isSeparator) {
        mName = name;
        mNumber = number;
        mIsSeparator = isSeparator;
    }

    public void setName(String name) {
        mName = name;
    }

    public void setNumber(String number) {
        mNumber = number;
    }

    public void setIsSection(boolean isSection) {
        mIsSeparator = isSection;
    }
}

Here’s how my performFiltering() method will look like:

protected FilterResults performFiltering(CharSequence constraint) {
    // Create a FilterResults object
    FilterResults results = new FilterResults();

    // If the constraint (search string/pattern) is null
    // or its length is 0, i.e., its empty then
    // we just set the `values` property to the
    // original contacts list which contains all of them
    if (constraint == null || constraint.length() == 0) {
        results.values = mContacts;
        results.count = mContacts.size();
    }
    else {
        // Some search copnstraint has been passed
        // so let's filter accordingly
        ArrayList<Contact> filteredContacts = new ArrayList<Contact>();

        // We'll go through all the contacts and see
        // if they contain the supplied string
        for (Contact c : mContacts) {
            if (c.mName.toUpperCase().contains( constraint.toString().toUpperCase() )) {
                // if `contains` == true then add it
                // to our filtered list
                filteredContacts.add(c);
            }
        }

        // Finally set the filtered values and size/count
        results.values = filteredContacts;
        results.count = filteredContacts.size();
    }

    // Return our FilterResults object
    return results;
}

I’ve tried to explain everything in the comments inside the code itself. Basically we’ve a mContacts object that contains all the contacts of our phone. Then if some constraint data is passed, we go through each and every contact to see if the name contains the string or not. If it does, then it’s stored in our filtered set. If the constraint is empty then we just set the value and count properties to the entire contacts data set.

Once all the filtering is done, the result is passed to the publishResults() method that publishes the results to the user interface. Here’s the code it should contain:

protected void publishResults(CharSequence constraint, FilterResults results) {
    mList = (ArrayList<Contact>) results.values;
    notifyDataSetChanged();
}

We just reset the data set to the filtered list. The mList variable is used to get data for each view in getView() method of the adapter. As soon as the data set is changed, we call notifyDataSetChanged() to refresh the Views.

We’ll have to do 1 final thing, which is to add the getFilter() method to our adapter implementing the Filterable interface, that should create and return the filter instance:

ContactsFilter mContactsFilter;

public Filter getFilter() {
    if (mContactsFilter == null)
        mContactsFilter = new ContactsFilter();

    return mContactsFilter;
}

I’ll just paste the entire code of my Activity that contains code to fetch all the contacts from the Contacts Provider, then display them in a list and finally add a searchbox to do the search using Custom Filters. This code is based off two of my earlier posts which are:

public class ContactsActivity extends Activity {

    String[] mProjection;
    CustomAdapter mAdapter;
    ArrayList<Contact> mContacts;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contacts);

        mProjection = new String[] {
                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER
        };

        final Cursor cursor = getContentResolver().query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                mProjection,
                null,
                null,
                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
        );

        int nameIndex = cursor.getColumnIndex(mProjection[0]);
        int numberIndex = cursor.getColumnIndex(mProjection[1]);

        mContacts = new ArrayList();

        int position = 0;
        boolean isSeparator = false;
        while(cursor.moveToNext()) {
            isSeparator = false;

            String name = cursor.getString(nameIndex);
            String number = cursor.getString(numberIndex);

            char[] nameArray;

            // If it is the first item then need a separator
            if (position == 0) {
                isSeparator = true;
                nameArray = name.toCharArray();
            }
            else {
                // Move to previous
                cursor.moveToPrevious();

                // Get the previous contact's name
                String previousName = cursor.getString(nameIndex);

                // Convert the previous and current contact names
                // into char arrays
                char[] previousNameArray = previousName.toCharArray();
                nameArray = name.toCharArray();

                // Compare the first character of previous and current contact names
                if (nameArray[0] != previousNameArray[0]) {
                    isSeparator = true;
                }

                // Don't forget to move to next
                // which is basically the current item
                cursor.moveToNext();
            }

            // Need a separator? Then create a Contact
            // object and save it's name as the section
            // header while pass null as the phone number
            if (isSeparator) {
                Contact contact = new Contact(String.valueOf(nameArray[0]), null, isSeparator);
                mContacts.add( contact );
            }

            // Create a Contact object to store the name/number details
            Contact contact = new Contact(name, number, false);
            mContacts.add( contact );

            position++;
        }

        // Creating our custom adapter
        mAdapter = new CustomAdapter(this, mContacts);

        // Create the list view and bind the adapter
        ListView listView = (ListView) findViewById(R.id.listview);
        listView.setAdapter(mAdapter);
    }

    public class Contact {
        public String mName;
        public String mNumber;
        public boolean mIsSeparator;

        public Contact(String name, String number, boolean isSeparator) {
            mName = name;
            mNumber = number;
            mIsSeparator = isSeparator;
        }

        public void setName(String name) {
            mName = name;
        }

        public void setNumber(String number) {
            mNumber = number;
        }

        public void setIsSection(boolean isSection) {
            mIsSeparator = isSection;
        }
    }

    public class CustomAdapter extends BaseAdapter implements Filterable {

        private Context mContext;
        private ArrayList<Contact> mList;

        // View Type for Separators
        private static final int ITEM_VIEW_TYPE_SEPARATOR = 0;
        // View Type for Regular rows
        private static final int ITEM_VIEW_TYPE_REGULAR = 1;
        // Types of Views that need to be handled
        // -- Separators and Regular rows --
        private static final int ITEM_VIEW_TYPE_COUNT = 2;

        ContactsFilter mContactsFilter;

        public CustomAdapter(Context context, ArrayList list) {
            mContext = context;
            mList = list;
        }

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

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return ITEM_VIEW_TYPE_COUNT;
        }

        @Override
        public int getItemViewType(int position) {
            boolean isSeparator = mList.get(position).mIsSeparator;

            if (isSeparator) {
                return ITEM_VIEW_TYPE_SEPARATOR;
            }
            else {
                return ITEM_VIEW_TYPE_REGULAR;
            }
        }

        @Override
        public boolean isEnabled(int position) {
            return getItemViewType(position) != ITEM_VIEW_TYPE_SEPARATOR;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            View view;

            Contact contact = mList.get(position);
            int itemViewType = getItemViewType(position);

            if (convertView == null) {
                LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

                if (itemViewType == ITEM_VIEW_TYPE_SEPARATOR) {
                    // If its a section ?
                    view = inflater.inflate(R.layout.contact_section_header, null);
                }
                else {
                    // Regular row
                    view = inflater.inflate(R.layout.contact_item, null);
                }
            }
            else {
                view = convertView;
            }


            if (itemViewType == ITEM_VIEW_TYPE_SEPARATOR) {
                // If separator

                TextView separatorView = (TextView) view.findViewById(R.id.separator);
                separatorView.setText(contact.mName);
            }
            else {
                // If regular

                // Set contact name and number
                TextView contactNameView = (TextView) view.findViewById(R.id.contact_name);
                TextView phoneNumberView = (TextView) view.findViewById(R.id.phone_number);

                contactNameView.setText( contact.mName );
                phoneNumberView.setText( contact.mNumber );
            }

            return view;
        }

        @Override
        public Filter getFilter() {
            if (mContactsFilter == null)
                mContactsFilter = new ContactsFilter();

            return mContactsFilter;
        }

        // Filter

        private class ContactsFilter extends Filter {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                // Create a FilterResults object
                FilterResults results = new FilterResults();

                // If the constraint (search string/pattern) is null
                // or its length is 0, i.e., its empty then
                // we just set the `values` property to the
                // original contacts list which contains all of them
                if (constraint == null || constraint.length() == 0) {
                    results.values = mContacts;
                    results.count = mContacts.size();
                }
                else {
                    // Some search copnstraint has been passed
                    // so let's filter accordingly
                    ArrayList<Contact> filteredContacts = new ArrayList<Contact>();

                    // We'll go through all the contacts and see
                    // if they contain the supplied string
                    for (Contact c : mContacts) {
                        if (c.mName.toUpperCase().contains( constraint.toString().toUpperCase() )) {
                            // if `contains` == true then add it
                            // to our filtered list
                            filteredContacts.add(c);
                        }
                    }

                    // Finally set the filtered values and size/count
                    results.values = filteredContacts;
                    results.count = filteredContacts.size();
                }

                // Return our FilterResults object
                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                mList = (ArrayList<Contact>) results.values;
                notifyDataSetChanged();
            }
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.contacts, menu);

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
        searchView.setSearchableInfo( searchManager.getSearchableInfo(getComponentName()) );

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return true; // handled
            }

            @Override
            public boolean onQueryTextChange(String newText) {

                mAdapter.getFilter().filter(newText);

                return true;
            }
        });

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

You’ll notice that the code to do the actual search in realtime is in the onCreateOptionsMenu():

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String query) {
        return true; // handled
    }

    @Override
    public boolean onQueryTextChange(String newText) {

        mAdapter.getFilter().filter(newText);

        return true;
    }
});

onQueryTextChange() is the callback listener that calls getFilter().filter(newText) on the Adapter object to do the filtering. Instead of using a SearchView (in the action bar) you could also use a TextView like EditText in your activity layout if you wish to. The addTextChangedListener() method can be used to add a TextWatcher whose methods will be called whenever the view’s text changes. Here’s how it’s going to be like:

EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        mAdapter.getFilter().filter(s.toString());
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
});

Using the inbuilt ArrayAdapter Filter

You can go through the subclasses list here to get to know which of the adapter implementations are Filterable. One of them is the most common ArrayAdapter adapter. Let’s see how to use the inbuilt filter of it. Here’s the onCreate() method from an Activity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_search_results);

    // List of items
    String[] items = {
            "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7",
            "Item 8", "Item 9", "Item 10", "Item 11", "Item 12", "Item 13", "Item 14",
            "Item 15"
    };
    // Create an array adapter
    final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, items);
    
    // set the adapter to the view
    setListAdapter(adapter);

    EditText editText = (EditText) findViewById(R.id.editText);
    editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            adapter.getFilter().filter(s);
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
}

Pretty simple, adapter.getFilter().filter(s) does the job for us. Just incase you’re wondering what the view would look like, then here it is:

<?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="match_parent"
    android:orientation="vertical">

    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText" />

    <ListView
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:id="@android:id/list">
        </ListView>
</LinearLayout>

There are certain limitations of this though (that I came across), like:

  • It does a “startsWith” check rather than “contains” with the constraint (search string) on the data set.
  • It is case insensitive.

To overcome these, we’ll have to write our own Filter implementation as described above in the “Implementing a Custom Filter” section.

Using the inbuilt SimpleCursorAdapter Filter

When working with cursor adapters like say SimpleCursorAdapter, we’ll need to do a few extra things.

  • First, we’ll have to define the index of the column in the cursor that can be used to get a string representation of the cursor. For this we just have to call the setStringConversionColumn() method on the adapter and pass it the cursor.getColumnIndex(COLUMN_NAME) value.
  • Secondly, the inbuilt filter does not implement any logic so we need to do that by ourselves. This has to be done by passing a FilterQueryProvider object to setFilterQueryProvider. The runQuery method will contain the logic to filter the current cursor. Generally it should run a cursor query by itself with the given constraint to return a new cursor that contains the filtered set.

Let’s work with our phone contacts again to see how we can filter it using SimpleCursorAdapter with the built-in CursorFilter being used as the filter class.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_search_results);

    // Columns from DB to map into the view file
    String[] fromColumns = {
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
            ContactsContract.CommonDataKinds.Phone.NUMBER
    };
    // View IDs to map the columns (fetched above) into
    int[] toViews = {
            R.id.contact_name,
            R.id.phone_number
    };

    final Cursor cursor = getContentResolver().query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null,
            null,
            null,
            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
    );

    final SimpleCursorAdapter adapter = new SimpleCursorAdapter(
            this, // context
            R.layout.contact_item, // layout file
            cursor, // DB cursor
            fromColumns, // data to bind to the UI
            toViews, // views that'll represent the data from `fromColumns`
            0
    );

    // Bind the adapter
    setListAdapter(adapter);

    adapter.setStringConversionColumn( cursor.getColumnIndex(fromColumns[0]) );
    adapter.setFilterQueryProvider(new FilterQueryProvider() {
        @Override
        public Cursor runQuery(CharSequence constraint) {

            if (constraint == null || constraint.length() == 0) {
                return getContentResolver().query(
                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null,
                        null,
                        null,
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
                );
            }
            else {
                return getContentResolver().query(
                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null,
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " LIKE ?",
                        new String[] { "%"+constraint+"%" },
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
                );
            }
        }
    });

    EditText editText = (EditText) findViewById(R.id.editText);
    editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            adapter.getFilter().filter(s);
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });
}

The most significant portion is this (which is what I explained above):

adapter.setStringConversionColumn( cursor.getColumnIndex(fromColumns[0]) );
adapter.setFilterQueryProvider(new FilterQueryProvider() {
    @Override
    public Cursor runQuery(CharSequence constraint) {

        if (constraint == null || constraint.length() == 0) {
            return getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,
                    null,
                    null,
                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
            );
        }
        else {
            return getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,
                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " LIKE ?",
                    new String[] { "%"+constraint+"%" },
                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
            );
        }
    }
});

Basically behind the scenes when adapter.getFilter().filter(s) is called, the performFiltering() method of the CursorFilter class executes runQueryOnBackgroundThread(constraint) method which executes the runQuery() method that we specified.

Note: If you’re working with CursorAdapter and not SimpleCursorAdapter then using setStringConversionColumn() might not be an option for you, since CursorAdapter doesn’t has that method. Instead you’ll have to implement the convertToString() method and work with that.

Here’s the performFiltering() and publishResults() pieces from the system’s CursorFilter class:

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        Cursor cursor = mClient.runQueryOnBackgroundThread(constraint);

        FilterResults results = new FilterResults();
        if (cursor != null) {
            results.count = cursor.getCount();
            results.values = cursor;
        } else {
            results.count = 0;
            results.values = null;
        }
        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        Cursor oldCursor = mClient.getCursor();
        
        if (results.values != null && results.values != oldCursor) {
            mClient.changeCursor((Cursor) results.values);
        }
    }

The runQueryOnBackgroundThread() in android’s CursorAdapter.java looks something like this:

    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
        if (mFilterQueryProvider != null) {
            return mFilterQueryProvider.runQuery(constraint);
        }

        return mCursor;
    }

Notice the if (constraint == null || constraint.length() == 0) { condition in our runQuery(). It checks for whether the constraint is empty or not, if yes then return the entire list of contacts. In this if block you might be tempted to do something like return adapter.getCursor() to simply return the cursor attached to the adapter previously, but then you might bump into an exception with the message attempted to access a cursor after it has been closed. This is because publishResults() calls changeCursor() that closes the previous/old cursor. If we keep on returning the same old cursor then it can’t be further used as it is closed, hence the exception is thrown.

Conclusion

Using filters, implementing functionalities like search becomes really easy on Android. Hopefully I was able to touch on various portions of the concept that’s really helpful as there isn’t much documentation available on the usage. Let me know if you have any questions in the comments.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

3 thoughts on “Android Realtime (Instant) Search with Filter Class and Filterable Interface Using Custom and Inbuilt Adapter”

  1. Hello Rishabh,

    I face this error sometimes

    E/AndroidRuntime: FATAL EXCEPTION: main
    PID: 26266
    java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(-1, class android.widget.ListPopupWindow$DropDownListView) with Adapter(class laceAutocompleteAdapter)]
    at android.widget.ListView.layoutChildren(ListView.java:1597)
    at android.widget.AbsListView.onLayout(AbsListView.java:2723)
    at android.view.View.layout(View.java:17938)
    at android.view.ViewGroup.layout(ViewGroup.java:5812)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
    at android.view.View.layout(View.java:17938)
    at android.view.ViewGroup.layout(ViewGroup.java:5812)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:344)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:281)
    at android.view.View.layout(View.java:17938)
    at android.view.ViewGroup.layout(ViewGroup.java:5812)
    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2666)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2367)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7397)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
    at android.view.Choreographer.doCallbacks(Choreographer.java:695)
    at android.view.Choreographer.doFrame(Choreographer.java:631)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:158)
    at android.app.ActivityThread.main(ActivityThread.java:7229)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

    1. here is my implementation to the filter function

      public Filter getFilter() {
      Filter filter = new Filter() {
      @Override
      protected FilterResults performFiltering(CharSequence constraint) {
      FilterResults results = new FilterResults();
      try {
      // Skip the autocomplete query if no constraints are given.
      /*if (constraint != null) {
      // Query the autocomplete API for the (constraint) search string.

      mResultList = getAutocomplete(constraint);
      if (mResultList != null) {
      // The API successfully returned results.
      results.values = mResultList;
      results.count = mResultList.size();
      }
      }*/

      if (constraint != null)
      // Query the autocomplete API for the (constraint) search string.
      mResultList = getAutocomplete(constraint);
      else
      mResultList = new ArrayList();
      results.values = mResultList;
      results.count = mResultList.size();
      return results;
      } catch (Exception ex) {
      Log.e(TAG,”performFiltering : “+ex.getMessage());
      }
      return results;
      }

      @Override
      protected void publishResults(CharSequence constraint, FilterResults results) {
      if (results != null && results.count > 0) {
      // The API returned at least one result, update the data.
      notifyDataSetChanged();
      } else {
      // The API did not return any results, invalidate the data set.
      notifyDataSetInvalidated();
      }
      }
      };
      return filter;
      }

      any suggestion would be helpfull !

Leave a Reply

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