Filtering ListView with custom (object) adapter

AndroidListviewFilterAdapter

Android Problem Overview


I'm trying to implement filtering of a ListView which is uses a custom object adapter, but I can't find any useful samples. The included code is very simplified, so no- keep in mind I can't use an regular ArrayAdapter. I have a EditText above the ListView, and when the user enters text in the EditText widget I would like to filter the ListView by the text written in the EditText. Any suggestions would be much appreciated!

Here is the snippet from the activity class:

public class management_objects extends Activity {

private static List<User> UserList;
private EfficientAdapter adapter = null;
private ListView objectListView = null;
private EditText SearchText = null;

private static class EfficientAdapter extends BaseAdapter implements Filterable{
	private LayoutInflater mInflater;	
	
	public EfficientAdapter(Context context) {
		mInflater = LayoutInflater.from(context);
	}
	
	public int getCount() {
		return UserList.size();
	}

	public Object getItem(int position) {
		return position;
	}

	public long getItemId(int position) {
		return position;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder; 
		if (convertView == null) { 
			convertView = mInflater.inflate(R.layout.imagelayout_2lines, null);
			holder = new ViewHolder();
			holder.text = (TextView) convertView.findViewById(R.id.managementObjectText);
			holder.subtext = (TextView) convertView.findViewById(R.id.managementObjectSubText);
			holder.icon = (ImageView) convertView.findViewById(R.id.managementObjectIcon);
			convertView.setTag(holder);
		}
		else {
			holder = (ViewHolder) convertView.getTag();
		}
		
		holder.text.setText(UserList.get(position).getFirstName());
		holder.subtext.setText(UserList.get(position).getLastName());
		holder.icon.setImageResource(R.drawable.user);
		
		return convertView;
	}
	
	static class ViewHolder { 
		TextView text;
		TextView subtext;
		ImageView icon;
	}

	@Override
	public Filter getFilter() {
		return null;
	}
}

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.adobjectlist);
	Bundle extras = getIntent().getExtras();
	
	SearchText = (EditText) findViewById(R.id.SearchBox);    
	SearchText.addTextChangedListener(filterTextWatcher);
	
	objectListView = (ListView) findViewById(R.id.ObjectList);
	objectListView.setOnItemClickListener(Item_Click);
	adapter = new EfficientAdapter(this);
	ComputerName = extras.getString("COMPUTER_NAME");
		
	//Get User list from webservice
	ShowUsers();
}

Here is The User class:

 public class User {
  private int UserId;
  private String FirstName;
  private String LastName;

	public int getUserId() {
		return UserId;
	}
	public void setUserId(int UserId) {
		this.UserId = UserId;
	}
	public String getFirstName() {
		return FirstName;
	}
	public void setFirstName(String FirstName) {
		this.FirstName = FirstName;
	}
	public String getLastName() {
		return LastName;
	}
	public void setLastName(String LastName) {
		this.LastName = LastName;
	}
}

Android Solutions


Solution 1 - Android

You need to do a few things:

  1. In your activity, register for a text change listener on your EditText that contains the value the user enters:

> mSearchValue.addTextChangedListener(searchTextWatcher);

  1. Create your searchTextWatcher and have it do something:

    private TextWatcher searchTextWatcher = new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // ignore }

     	@Override
     	public void beforeTextChanged(CharSequence s, int start, int count, int after) {
     		// ignore
     	}
    
     	@Override
     	public void afterTextChanged(Editable s) {
     		Log.d(Constants.TAG, "*** Search value changed: " + s.toString());
     		adapter.getFilter().filter(s.toString());
     	}
     };
    
  2. Override getFilter() in your custom adapter and have it filter the results and notify the listview that the dataset has changed.

     @Override
     public Filter getFilter() {
     	return new Filter() {
     		@SuppressWarnings("unchecked")
     		@Override
     		protected void publishResults(CharSequence constraint, FilterResults results) {
     			Log.d(Constants.TAG, "**** PUBLISHING RESULTS for: " + constraint);
     			myData = (List<MyDataType>) results.values;
     			MyCustomAdapter.this.notifyDataSetChanged();
     		}
    
     		@Override
     		protected FilterResults performFiltering(CharSequence constraint) {
     			Log.d(Constants.TAG, "**** PERFORM FILTERING for: " + constraint);
     			List<MyDataType> filteredResults = getFilteredResults(constraint);
    
     			FilterResults results = new FilterResults();
     			results.values = filteredResults;
    
     			return results;
     		}
     	};
     }
    

Solution 2 - Android

Here an interesting example

public Filter getFilter() {
	return new Filter() {

		@Override
		protected FilterResults performFiltering(CharSequence constraint) {
			final FilterResults oReturn = new FilterResults();
			final ArrayList<station> results = new ArrayList<station>();
			if (orig == null)
				orig = items;
			if (constraint != null) {
				if (orig != null && orig.size() > 0) {
					for (final station g : orig) {
						if (g.getName().toLowerCase()
								.contains(constraint.toString()))
							results.add(g);
					}
				}
				oReturn.values = results;
			}
			return oReturn;
		}

		@SuppressWarnings("unchecked")
		@Override
		protected void publishResults(CharSequence constraint,
				FilterResults results) {
			items = (ArrayList<station>) results.values;
			notifyDataSetChanged();
		}
	};
}

public void notifyDataSetChanged() {
	super.notifyDataSetChanged();
	notifyChanged = true;
}

Solution 3 - Android

For those who don't need the Filterable interface, there is a much simpler solution. This also handles notifyDataSetChanged() correctly where the other solutions fail. Note that you need to add a getArray() function to the BaseAdapter that just returns the array object that was passed to the constructor.

public abstract class BaseFilterAdapter<T> extends BaseAdapter<T> {

    private List<T> original;
    private String lastFilter;

    public BaseFilterAdapter(Context context, List<T> array) {
        super(context, new LinkedList<T>());
        original = array;
        filter("");
    }

    protected abstract Boolean predicate(T element, String filter);

    public void filter(String filter) {
        lastFilter = filter;
        super.getArray().clear();
        for (T element : original)
            if (predicate(element, filter))
                super.getArray().add(element);
        super.notifyDataSetChanged();
    }

    @Override
    public List<T> getArray() {
        return original;
    }

    @Override
    public void notifyDataSetChanged() {
        filter(lastFilter);
    }
}

Solution 4 - Android

Add toString override on your base class. For example

@Override
public String toString() {
    return this.name;
}

Above makes your List as string list. So you can use:

your_edit_text.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
        YourActivity.this.YourAdapter.getFilter().filter(arg0);
    }

    @Override
    public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
    }

    @Override
    public void afterTextChanged(Editable arg0) {    
    }
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGeirFrimannView Question on Stackoverflow
Solution 1 - AndroidDustinBView Answer on Stackoverflow
Solution 2 - AndroidyanddamView Answer on Stackoverflow
Solution 3 - AndroiddanijarView Answer on Stackoverflow
Solution 4 - AndroidGereltodView Answer on Stackoverflow