What's brewing in the mobile world?

Seattle, WA / 45°

ArrayAdapter Filtering and You

Having a list of things you can view on your phone is a no brainer. Contacts, images, vacation spots; the Android ArrayAdapter widget gives us an easy way to do this. Now the examples given inside the docs is a nice example if we’re just looking at a list of strings but stops way short of being useful to those of us feeding an ArrayList to the adapter.

After looking around you’ll start finding a variety of ways to accomplish filtering. Here is how we accomplished this task filtering an ArrayList of objects. The key was to look at the ArrayAdapter source and make sure some key functions are overriden. It was frustrating having to search through poor examples, vague references, and lame answers on forums.

Firstly, you’ll need to make sure your custom adapter has this feature enabled.

getListView().setTextFilterEnabled(true);

Next when you set up your onItemClickListener make sure you are pointing to the ArrayList that was passed to the adapter and set inside. There maybe a better way of accomplishing this instead of making mItems public but for now this’ll work.

    private OnItemClickListener contactListItemClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        	ItemObject item = mItemObjectAdapter.mItems.get(position);
            Intent intent = new Intent(ActivityItems.this, ActivityDetails.class);
            intent.putExtra(EXTRA_STUFF, item.getId());
            startActivity(intent);
		}
    };

Start your custom adapter by implementing the Filterable interface. You will need to override all the functions specifically dealing with grabbing things from your ArrayList. The getView is the given one.

private class ContactAdapter extends ArrayAdapter<Contact> implements Filterable {
    	private final Object mLock = new Object();
    	private ItemsFilter mFilter;
        public ArrayList<Item> mItems;
        /*  ...   */
        @Override
        public int getCount() {
        	return mItems.size();
        }
        @Override
        public Item getItem(int position) {
            return mItems.get(position);
        }
        @Override
        public int getPosition(Item item) {
            return mItems.indexOf(item);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        /*  ...   */

And now for the Filtering. So implementing this interface is a easy. It is one function. Notice how we declare mFilter above.

        /**
         * Implementing the Filterable interface.
         */
        public Filter getFilter() {
            if (mFilter == null) {
                mFilter = new ItemsFilter();
            }
            return mFilter;
}

Now the actual filter that is returned is all you. There are some basics here. We extend Filter which if you check out the code requires to only worry about two abstract functions. Here they are:

        /**
         * Custom Filter implementation for the items adapter.
         *
         */
        private class ItemsFilter extends Filter {
            protected FilterResults performFiltering(CharSequence prefix) {
            	// Initiate our results object
            	FilterResults results = new FilterResults();
            	// If the adapter array is empty, check the actual items array and use it
                if (mItems == null) {
                    synchronized (mLock) { // Notice the declaration above
                    	mItems = new ArrayList<Item>(mItemsArray);
                    }
                }
                // No prefix is sent to filter by so we're going to send back the original array
            	if (prefix == null || prefix.length() == 0) {
            		synchronized (mLock) {
            			results.values = mItemsArray;
                        results.count = mItemsArray.size();
            		}
            	} else {
                        // Compare lower case strings
            		String prefixString = prefix.toString().toLowerCase();
            		// Local to here so we're not changing actual array
                    final ArrayList<Item> items = mItems;
                    final int count = items.size();
                    final ArrayList<Item> newItems = new ArrayList<Item>(count);
                    for (int i = 0; i < count; i++) {
                        final Item item = items.get(i);
                        final String itemName = item.getItemname().toString().toLowerCase();
                        // First match against the whole, non-splitted value
                        if (itemName.startsWith(prefixString)) {
                            newItems.add(item);
                        } else {} /* This is option and taken from the source of ArrayAdapter
                            final String[] words = itemName.split(" ");
                            final int wordCount = words.length;
                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newItems.add(item);
                                    break;
                                }
                            }
                        } */
                    }
                    // Set and return
                    results.values = newItems;
                    results.count = newItems.size();
                }
            	return results;
            }
            @SuppressWarnings("unchecked")
            protected void publishResults(CharSequence prefix, FilterResults results) {
                //noinspection unchecked
            	mItems = (ArrayList<Item>) results.values;
            	// Let the adapter know about the updated list
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
}

As long as the ArrayAdapter is properly overridden then things should work as expected. The keys are to enable the behavior, override, implement the interface, and then make available the class that will be used for filtering. The filtering logic itself is fairly straight forward. Since we expect a filter to be requested, if no string (or as we called it prefex) is passed then return the full unadulterated list of items. Otherwise, grab whatever detail you are filtering against and compare its start to your input prefix string.

Hope that helps someone.

Comments are closed.