1

I have a list view with a search EditText above it. Each row in the list view consists of several texts and pictures. When the user enters a keyword in the search EditText, I want the listview to be updated with the rows that contain the keyword. I have borrowed the implementation in this question as below.

My Custom Adapter:

public class CustomAdapter extends BaseAdapter implements Filterable  {

    private LayoutInflater inflater;
    private ArrayList<CustomObject> objects;//mOriginalValues
    private Activity activity;
    private ArrayList<Bitmap> newsImageList;
    private Context context;
    private ArrayList<CustomObject> filteredObjects;    // Values to be displayed after filtering


    private class ViewHolder {
        TextView titleTextView;
        TextView dateTextView;
        TextView bodyTextView;
        ImageView logoView;
        ImageView newsImageView;
        ProgressBar progressBar;
    }


    public CustomAdapter(Context context, ArrayList<CustomObject> objects, Activity activity) {
       // super(context, R.layout.news_item, objects);

        inflater = LayoutInflater.from(context);
        this.objects = objects;
        this.filteredObjects=objects;
        this.activity= activity;
        this.newsImageList= new ArrayList<Bitmap>();
        this.context= context;

        for(int i=0; i<objects.size(); i++)
            newsImageList.add(null);


    }

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

    @Override
    public CustomObject getItem(int position) {
        return filteredObjects.get(position);
    }

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

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

        System.out.println("IN GET VIEW");

        ViewHolder holder = null;
        if(convertView == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(R.layout.news_item, null);

            holder.titleTextView = (TextView) convertView.findViewById(R.id.txtTitle);
            holder.dateTextView = (TextView) convertView.findViewById(R.id.txtDate);
            holder.bodyTextView = (TextView) convertView.findViewById(R.id.txtBody);
            holder.logoView= (ImageView)  convertView.findViewById(R.id.source_imageView);
            holder.newsImageView= (ImageView) convertView.findViewById(R.id.news_imageView);
            holder.progressBar= (ProgressBar) convertView.findViewById(R.id.img_progress_bar);


            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.titleTextView.setText(objects.get(position).getTitle());
        holder.dateTextView.setText(objects.get(position).getDate());
        holder.bodyTextView.setText(objects.get(position).getBody());

        //for the source logo
        String uri = "@drawable/"+objects.get(position).getSource_logo()+"_logo";
        int imageResource = activity.getResources().getIdentifier(uri, null, activity.getPackageName());
        Drawable res =activity.getResources().getDrawable(imageResource);
        holder.logoView.setImageDrawable(res);



        //the news image
        String newsImageURL = objects.get(position).getNewsImageURL();

        if(newsImageURL!=null) {
            // show The Image in a ImageView

            //pre-execute
            holder.progressBar.setVisibility(View.VISIBLE);
            holder.newsImageView.setVisibility(View.VISIBLE);
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.bodyTextView.getLayoutParams();
            params.addRule(RelativeLayout.BELOW, R.id.relative_container);

            Picasso.with(context).load(newsImageURL).into(holder.newsImageView);//EXECUTE in background

            holder.progressBar.setVisibility(View.INVISIBLE);//post-execute

        }
        else
        {
            System.out.println("NO IMAGE");
            //adjusting the view
            holder.progressBar.setVisibility(View.INVISIBLE);
            holder.newsImageView.setVisibility(View.GONE);
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.bodyTextView.getLayoutParams();
            params.addRule(RelativeLayout.BELOW, R.id.txtDate);
        }


        return convertView;
    }



    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {

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

                filteredObjects = (ArrayList<CustomObject>) results.values; // has the filtered values
                notifyDataSetChanged();  // notifies the data with new filtered values
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {

                System.out.println("FILTERING with the keyword:"+ constraint);

                FilterResults results = new FilterResults();        // Holds the results of a filtering operation in values
                ArrayList<Object> FilteredArrList = new ArrayList<Object>();

                if (objects == null) {
                    objects = new ArrayList<CustomObject>(filteredObjects); // saves the original data in mOriginalValues
                }

                /********
                 *
                 *  If constraint(CharSequence that is received) is null returns the mOriginalValues(Original) values
                 *  else does the Filtering and returns FilteredArrList(Filtered)
                 *
                 ********/
                if (constraint == null || constraint.length() == 0) {

                    // set the Original result to return
                    results.count = objects.size();
                    results.values = objects;
                } else {
                    constraint = constraint.toString().toLowerCase();
                    for (int i = 0; i < objects.size(); i++) {

                        System.out.println("loop "+i);

                        String title = objects.get(i).getTitle();
                        String date = objects.get(i).getDate();
                        String body = objects.get(i).getBody();
                        String source_logo = objects.get(i).getSource_logo();
                        String newsImageURL = objects.get(i).getNewsImageURL();

                        System.out.println("title of this item:"+title+", key word:"+constraint.toString());
                        if (title.contains(constraint.toString()) || date.contains(constraint.toString()) || body.contains(constraint.toString())) {
                            System.out.println("FOUND!");
                            FilteredArrList.add(new CustomObject(title,date, body, source_logo, newsImageURL));
                        }
                    }
                    // set the Filtered result to return
                    results.count = FilteredArrList.size();
                    results.values = FilteredArrList;
                }
                return results;
            }


        };
        return filter;
    }

}

class CustomObject {

    private String title;
    private String date;
    private String body;
    private String source_logo;
    private String newsImageURL;

    public CustomObject(String prop1, String prop2, String prop3, String prop4, String prop5) {
        this.title = prop1;
        this.date = prop2;
        this.body=prop3;
        this.source_logo=prop4;
        this.newsImageURL=prop5;
    }

    public String getTitle() {
        return title;
    }

    public String getDate() {
        return date;
    }

    public String getBody(){
        return body;
    }

    public String getSource_logo()
    {
        return source_logo;
    }

    public String getNewsImageURL(){ return newsImageURL; }

}

In my activity:

listView = (ListView) activity.findViewById(R.id.activity_main_listview);//news list view (title, date, body per item)
            searchEditText = (EditText) activity.findViewById(R.id.news_search_editText);

            // Add Text Change Listener to EditText
            searchEditText.addTextChangedListener(new TextWatcher() {

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    // Call back the Adapter with current character to Filter
                    System.out.println("TEXT CHANGED!");
                    customAdapter.getFilter().filter(s.toString());
                    //listView.setAdapter(customAdapter);
                }

                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }

                @Override
                public void afterTextChanged(Editable s) {

                    //customAdapter.getFilter().filter(s.toString());
                }
            });


            //preparing the news titles, dates, first part of bodies, and source logo.
            ArrayList<CustomObject> objects = new ArrayList<CustomObject>();
            for (int i = 0; i < titleList.size(); i++)
                objects.add(new CustomObject(titleList.get(i), dateList.get(i), reduceTextLetters(textList.get(i), 150) + "...", sourceList.get(i), imageURLList.get(i)));

            customAdapter = new CustomAdapter(activity, objects, activity);
            listView.setAdapter(customAdapter);

My Activity XML file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <RelativeLayout
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">

        <EditText
            android:id="@+id/news_search_editText"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="البحث"
            />

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/activity_main_swipe_refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/news_search_editText">

        <ListView
            android:id="@+id/activity_main_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
        </ListView>


        </android.support.v4.widget.SwipeRefreshLayout>


    </RelativeLayout>

    <Button
        android:text="تحديث ↑"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/refresh_button"
        android:background="@drawable/button_refresh"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:layout_marginTop="50dp"
        />

</RelativeLayout>

Unfortunately, the behavior of the filtering is not correct. Although I can see in the log that the comparison between the keyword and the content in each row is done correctly, and only matching keywords are being added to the filtered list, the updated list is not right. When I enter a keyword that is in the list, It displays for me the first few items in the list (some of which has nothing to do with the keyword) and removes the rest. When I enter a keyword that is not in the list, it clears the listview. Therefore, the behavior is strange and unexpected. Can someone suggest a solution?

Community
  • 1
  • 1
Traveling Salesman
  • 1,901
  • 8
  • 36
  • 66

2 Answers2

0

There is an another approach- save all possible values to database search matching values by Sql's LIKE request. I used that for implementing search in a very long list of cities. Here is a snippet of code for doing so (prject is using OrmLite):

 /**
     * Query all objects from database, which field contains given substring.
     * @param daoFetcher    Function, returning Dao of necessary type
     * @param targetColumnName Name of Table column, we're looking matches in for
     * @param matchText text within table column we're searching for.
     * @param <T>
     * @return
     */
    private <T>  List<T> getRecordsContainingText(Func0<Dao<T, Integer>> daoFetcher,
                                                  String targetColumnName,
                                                  String matchText) throws SQLException {
        // get dao of appropriate type by using user function
        Dao<T, Integer> dao = daoFetcher.call();
        QueryBuilder<T, Integer> queryBuilder = dao.queryBuilder();
        // query for 'LIKE'
        String likeQuery = "%" + matchText + "%";
        // form query
        queryBuilder.where().like(targetColumnName, likeQuery);
        PreparedQuery<T> preparedQuery = queryBuilder.prepare();
        List<T> result = dao.query(preparedQuery);
        return result;
    }
Alex Shutov
  • 3,128
  • 2
  • 11
  • 11
0

I solved my problem by creating a copy of the list values. Whenever the user enters a keyword, this copy is cleared and then populated from the original list only with the rows that has the keyword. Finally, I reset my adapter with the new values in the copy list. Something like below,

            //another way (for keyword search)
        // Add Text Change Listener to EditText
        searchEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Call back the Adapter with current character to Filter
                System.out.println("TEXT CHANGED!");
                //customAdapter.getFilter().filter(s.toString());
                //listView.setAdapter(customAdapter);


                //another way (for keyword search)

                titleList2.clear();
                dateList2.clear();
                textList2.clear();
                urlList2.clear();
                sourceList2.clear();
                timestampList2.clear();
                imageURLList2.clear();

                for(int i=0; i<textList.size(); i++)
                {
                    if( s.toString().isEmpty() || textList.get(i).contains(s) || titleList.get(i).contains(s) || dateList.get(i).contains(s) || sourceList.get(i).contains(s))
                    {

                        titleList2.add(titleList.get(i));
                        dateList2.add(dateList.get(i));
                        textList2.add(textList.get(i));
                        urlList2.add(urlList.get(i));
                        sourceList2.add(sourceList.get(i));
                        timestampList2.add(timestampList.get(i));
                        imageURLList2.add(imageURLList.get(i));

                    }

                }


                //preparing the news titles, dates, first part of bodies, and source logo.
                ArrayList<CustomObject> objects = new ArrayList<CustomObject>();
                for (int i = 0; i < titleList2.size(); i++)
                    objects.add(new CustomObject(titleList2.get(i), dateList2.get(i), reduceTextLetters(textList2.get(i), 150) + "...", sourceList2.get(i), imageURLList2.get(i)));

                customAdapter = new CustomAdapter(activity, objects, activity);
                listView.setAdapter(customAdapter);




            }

It is a simple way, and I find it very intuitive.

Traveling Salesman
  • 1,901
  • 8
  • 36
  • 66