Search code examples
androidandroid-listviewlistadapter

Custom ArrayAdapter is not updating when filtering


I have implemented a CustomArrayAdapter from this tutorial to have a thumb nail and 2 lines for each entry of the list view. And it works perfectly except for the filtering. I've been trying to implement the filtering following this, this and this questions, but the listview never gets updated.

This is my code for the multiline layout:

<?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="?android:attr/listPreferredItemHeight"
android:padding="6dip">

<ImageView
    android:id="@+id/thumb"
    android:background="@drawable/thumb"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:layout_marginRight="6dip"
    android:layout_marginTop="2dip"
    android:layout_marginBottom="2dip"/>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="fill_parent">

    <TextView
        android:id="@+id/custom_title"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:textColor="@color/black"
        android:gravity="center_vertical"
        />

    <TextView  
        android:id="@+id/desc"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" 
        android:textColor="@color/darkgray"
        android:textStyle="italic"
        android:singleLine="true"
        android:ellipsize="marquee"
         />

</LinearLayout>

and this is my code for my customListAdapter (following the first link)

public class CustomListAdapter extends ArrayAdapter implements Filterable{

private static ArrayList<CustomListItem> searchArrayList;
private static ArrayList<CustomListItem> subItems;
private PTypeFilter filter;


 private LayoutInflater mInflater;

 public CustomListAdapter(Context context, int textViewResourceId, ArrayList<CustomListItem> results) {
     super(context,textViewResourceId, results);
  searchArrayList = results;
  mInflater = LayoutInflater.from(context);
 }
 public int getCount() {
      return searchArrayList.size();
     }

     public Object getItem(int position) {
      return searchArrayList.get(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.custom_row, null);
       holder = new ViewHolder();
       holder.txtTitle = (TextView) convertView.findViewById(R.id.custom_title);
       holder.txtDesc = (TextView) convertView.findViewById(R.id.desc);
       holder.thumb = (ImageView) convertView.findViewById(R.id.thumb);

       convertView.setTag(holder);
      } else {
       holder = (ViewHolder) convertView.getTag();
      }

      holder.txtTitle.setText(searchArrayList.get(position).getTitle());
      holder.txtDesc.setText(searchArrayList.get(position).getDesc());
      holder.thumb.setImageBitmap(searchArrayList.get(position).getThumb());
      //holder.thumb.setBackgroundColor(Color.BLACK);

      return convertView;
     }

     static class ViewHolder {
      TextView txtTitle;
      TextView txtDesc;
      ImageView thumb;
     }       

     @Override
     public Filter getFilter() {
         Log.i("CustomListAdapter", "testing filter");
         if (filter == null){
           filter  = new PTypeFilter();
         }
         return filter;
       }

  private class PTypeFilter extends Filter{


@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence prefix,
                              FilterResults results) {
  // NOTE: this function is *always* called from the UI thread.
   subItems =  (ArrayList<CustomListItem>)results.values;

    notifyDataSetChanged();
}

@SuppressWarnings("unchecked")
protected FilterResults performFiltering(CharSequence prefix) {
      // NOTE: this function is *always* called from a background thread, and
      // not the UI thread. 

      FilterResults results = new FilterResults();
      ArrayList<CustomListItem> i = new ArrayList<CustomListItem>();

      if (prefix!= null && prefix.toString().length() > 0) {

          for (int index = 0; index < searchArrayList.size(); index++) {
              CustomListItem si = searchArrayList.get(index);
              if(si.toString().compareTo(prefix.toString()) == 0){
                i.add(si);  
              }
          }
          results.values = i;
          results.count = i.size();                   
      }
      else{
          synchronized (searchArrayList){
              results.values = searchArrayList;
              results.count = searchArrayList.size();
          }
      }

      return results;
   }
 }     
}

And from my main activity I call custom base adapter like this:

adapter = new CustomListAdapter(this, R.id.custom_title, DATA);
setListAdapter(adapter);

(not 100% sure that R.id.custom_title is the id I should be putting there.

I have added debug messages and TextChange is being called, as the getFilter() method. So I don't know what I am doing wrong. Before I switched from ListAdapter to CustomListAdapter, the filtering was working fine :(

THanks for your help


Solution

  • The ListView isn't filtered because you set the results of the filtering on subItems which is not used in the adapter class so the call to notifyDataSetChanged will not work as from the adapter's point of view the data is intact. You're correct to use another list to hold the old values, they will be required so you have all the old values around. To solve the problem add the line :

    subItems = new ArrayList<CustomListItem>(searchArrayList);
    

    in the adapter's constructor, this is so you have a copy of the full values to use later. Then in the publishResults method use the searchArrayList list, because that is the list that backs the adapter:

    searchArrayList =  (ArrayList<CustomListItem>)results.values;
    notifyDataSetChanged();
    

    Then in the performFiltering method:

      if (prefix!= null && prefix.toString().length() > 0) {
          // use the initial values !!! 
          for (int index = 0; index < subItems.size(); index++) {
              CustomListItem si = subItems.get(index);
              final int length = prefix.length();
              // if you compare the Strings like you did it will never work as you compare the full item string(you'll have a match only when you write the EXACT word)
              // keep in mind that you take in consideration capital letters!
              if(si.toString().substring(0, prefixLength).compareTo(prefix.toString()) == 0){
                i.add(si);  
              }
          }
          results.values = i;
          results.count = i.size();                   
      }
      else{
          // revert to the old values 
          synchronized (searchArrayList){
              results.values = subItems;
              results.count = subItems.size();
          }
      }
    

    I hope it works.

    (not 100% sure that R.id.custom_title is the id I should be putting there.

    It doesn't matter as you use your own custom adapter.