Get size of filtered array in AutoCompleteTextview

I am working on a project where a user can search for data. For this I have implemented AutoCompleteTextView

.

 autoComplete.setAdapter(new ArrayAdapter<String>(CheckRiskActivity.this,
                                        R.layout.auto_text_row, druglist));
                                autoComplete.setThreshold(1);
//druglist is my arraylist

      

The text change listener looks like this:

autoComplete.addTextChangedListener(new TextWatcher() {

        @Override
        public void afterTextChanged(Editable s) {
            // here I want to get the size of filtered array list every time when the user adds any character.
        }

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

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

      

Explanation: If the size of my initial array is 100 and if the user types 'a' then I want to get the size of the filtered array.

Note: I tried autoComplete.getAdapter().getCount();

it but it gives the actual result after adding another character.

+3


source to share


5 answers


You cannot get the correct count of the filtered items in TextWatcher

because filtering usually takes longer than TextWatcher

event listeners. So you get the wrong one autoComplete.getAdapter().getCount()

in afterTextChanged()

. I would recommend using a custom listener that will be called every time the filtered items change.

I will give two similar approaches, using separate classes and using only 1 class.

APPROACH 1 : Your adapter should look like this:

import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import java.util.ArrayList;
import java.util.List;

public class AutoCompleteAdapter extends ArrayAdapter
{
    private List<String>    tempItems;
    private List<String>    suggestions;
    private FilterListeners filterListeners;

    public AutoCompleteAdapter(Context context, int resource, List<String> items)
    {
        super(context, resource, 0, items);

        tempItems = new ArrayList<>(items);
        suggestions = new ArrayList<>();
    }

    public void setFilterListeners(FilterListeners filterFinishedListener)
    {
        filterListeners = filterFinishedListener;
    }

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

    Filter nameFilter = new Filter()
    {
        @Override
        protected FilterResults performFiltering(CharSequence constraint)
        {
            if (constraint != null)
            {
                suggestions.clear();
                for (String names : tempItems)
                {
                    if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
                    {
                        suggestions.add(names);
                    }
                }
                FilterResults filterResults = new FilterResults();
                filterResults.values = suggestions;
                filterResults.count = suggestions.size();
                return filterResults;
            }
            else
            {
                return new FilterResults();
            }
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results)
        {
            List<String> filterList = (ArrayList<String>) results.values;

            if (filterListeners != null && filterList!= null)
                filterListeners.filteringFinished(filterList.size());

            if (results != null && results.count > 0)
            {
                clear();
                for (String item : filterList)
                {
                    add(item);
                    notifyDataSetChanged();
                }
            }
        }
    };
}

      

The interface that will be used to inform you when filtering will be complete:

public interface FilterListeners
{
    void filteringFinished(int filteredItemsCount);
}

      

And you can use it:



import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.AutoCompleteTextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity implements FilterListeners
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
        autoComplete.setThreshold(1);

        List<String> stringList = new ArrayList<>();
        stringList.add("Black");
        stringList.add("White");
        stringList.add("Yellow");
        stringList.add("Blue");
        stringList.add("Brown");


        final AutoCompleteAdapter adapter = new AutoCompleteAdapter(this, android.R.layout.simple_list_item_1, stringList);
        adapter.setFilterListeners(this);
        autoComplete.setAdapter(adapter);
    }

    @Override
    public void filteringFinished(int filteredItemsCount)
    {
        Log.i("LOG_TAG", " filteringFinished  count = " + filteredItemsCount);
    }
}

      

APPROACH 2 :

    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.ArrayAdapter;
    import android.widget.AutoCompleteTextView;
    import android.widget.Filter;
    import java.util.ArrayList;
    import java.util.List;

    public class MainActivity extends Activity
    {
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            final AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
            autoComplete.setThreshold(1);

            final List<String> stringList = new ArrayList<>();
            stringList.add("Black");
            stringList.add("White");
            stringList.add("Yellow");
            stringList.add("Blue");
            stringList.add("Brown");

            final ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, stringList)
            {
                private List<String> tempItems   = stringList;
                private List<String> suggestions = new ArrayList<>();

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

                Filter nameFilter = new Filter()
                {
                    @Override
                    protected FilterResults performFiltering(CharSequence constraint)
                    {
                        if (constraint != null)
                        {
                            suggestions.clear();
                            for (String names : tempItems)
                            {
                                if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
                                {
                                    suggestions.add(names);
                                }
                            }
                            FilterResults filterResults = new FilterResults();
                            filterResults.values = suggestions;
                            filterResults.count = suggestions.size();
                            return filterResults;
                        }
                        else
                        {
                            return new FilterResults();
                        }
                    }

                @Override
                protected void publishResults(CharSequence constraint, FilterResults results)
                {
                    List<String> filterList = (ArrayList<String>) results.values;
                    filteringFinished(filterList.size());

                    if (results != null && results.count > 0)
                    {
                        clear();
                        for (String item : filterList)
                        {
                            add(item);
                            notifyDataSetChanged();
                        }
                    }
                }
            };
        };

        autoComplete.setAdapter(arrayAdapter);
    }

    private void filteringFinished(int filteredItemsCount)
    {
        Log.i("LOG_TAG", " filteringFinished  count = " + filteredItemsCount);
    }
}

      

filteringFinished()

the method will be called when you enter something in the autocomplete input box and is filtered.

UPDATE (search by Trie):

I created a Github project with a simple example of using the Trie search algorithm to dramatically increase autocomplete performance.

https://github.com/saqada/android-AutoCompleteWithTrie

+3


source


Basically, we have to implement Filterable in Adapter class

public class DrugListAdapter extends BaseAdapter implements
    Filterable {
Context context;
LayoutInflater inflater;
drugsFilter drugsFilter;

List<Drug> drugList = new ArrayList<>();
private List<Drug> drugListOrig;

public DrugListAdapter(Context context,
        List<Drug> drugList) {
    super();

    this.context = context;
    this.drugList = drugList;
    this.drugListOrig = new ArrayList<>(
            drugList);
    inflater = LayoutInflater.from(context);
}

public void resetData() {
    drugList = drugListOrig;
}



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

@Override
public Drug getItem(int position) {
    return drugList.get(position);
}

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

private class ViewHolder {
    TextView mVendorName;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    ViewHolder viewHolder;
    Drug item = drugList.get(position);
    if (view == null) {
        viewHolder = new ViewHolder();
        view = inflater.inflate(R.layout.item_drug,
                parent, false);
        viewHolder.mVendorName = (TextView) view
                .findViewById(R.id.item_drug_drug_name);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();

    }
    viewHolder.mVendorName.setText(item.getDrug_name());
    return view;
}

@Override
public Filter getFilter() {
    if (drugsFilter == null) {
        drugsFilter = new DrugsFilter();
    }
    return drugsFilter;
}

public class DrugsFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults results = new FilterResults();
        // We implement here the filter logic
        if (constraint == null || constraint.length() == 0) {
            // No filter implemented we return all the list
            results.values = drugListOrig;
            results.count = drugListOrig.size();
        } else {
            // We perform filtering operation
            List<Drug> sList = new ArrayList<>();
            for (Drug p : drugList) {
                if (p.getDrug_name().toUpperCase()
                        .startsWith(constraint.toString().toUpperCase()))
                    sList.add(p);
            }

            results.values = sList;
            results.count = sList.size();

        }
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint,
            FilterResults results) {
        if (results.count == 0)
            notifyDataSetInvalidated();
        else {
            drugList = (List<Drug>) results.values;
            notifyDataSetChanged();
        }

    }

}

      

}



This part is for EditText

andTextWatcher

    String m;
    mDrugEditText.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before,
                                  int count) {

            if (count < before) {
                adapter.resetData();
                adapter.notifyDataSetChanged();
            }
            adapter.getFilter().filter(s.toString());

        }

        @Override
        public void beforeTextChanged(CharSequence s, int start,
                                      int before, int count) {
            if (s.length() == 0 || s.length() == 1) {
                mDrugEditText.invalidate();
            }
            if (s.length() == 3) {
                if (mDrugEditText
                        .isPerformingCompletion()) {
                    return;
                }
                adapter.resetData();
                adapter.notifyDataSetChanged();

            }
        }

        @Override
        public void afterTextChanged(Editable s) {
            m = s.toString();
            adapter.getFilter().filter(s.toString());
        }
    });

      

+1


source


I am assuming you have gone through the basic search options available in android / java and you are not satisfied with the results.

If you don't want to go through the entire list every time the text changes, the only way is to implement a data structure that does this.

The obvious solution would be three. read this to get an idea of ​​trie

This is now working on the concept of pre-processing data before searching. Since you have limited elements it shouldn't take long and you can do it when the page loads.

Steps - - Process and index all items on load. Place the indices on a k-ary tree (it will be 32-ary, each character will be an alphabet). - on text change - go to node and get counter. It will take O (1).

I believe this is the fastest thing you can do.

The above will work best if you have words indexed or if you just need to run startswith.

0


source


Sa Qada's answer is a very good approach. However, my answer below gave me the best performance in my case.

autoCompleteTextViewCheckRisk.setAdapter(new ArrayAdapter<String>
            (CheckRiskActivity.this, R.layout.auto_text_row, druglist));
//druglist is the Arraylist of String.
autoCompleteTextViewCheckRisk.setThreshold(1);

      

Text Change Listener:

autoCompleteTextViewCheckRisk.addTextChangedListener(new TextWatcher() {

        @Override
        public void afterTextChanged(Editable s) {
            filter(druglist, s.toString());
        }

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

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {

        }
    });

      

Filtration method:

private void filter(ArrayList<String> originalArrayList, String query) {

    query = query.toLowerCase();
    filteredArrayList.clear();
    //filtered arraylist is also Arraylist of String, Just declared as global
    for (String itemName : originalArrayList) {
        final String text = itemName.toLowerCase();
        if (text.startsWith(query)) {
            filteredArrayList.add(itemName);
        }
    }

    if (filteredArrayList.size() == 0) {
        Log.i(TAG, "filter: No data found");
    }
}

      

0


source


as per Ayaz Alifov's answer, you cannot get the correct number of filtered items in TextWatcher

because filtering usually takes longer than event listeners TextWatcher

.

but i did the trick with timerTask

. so the TextWatcher will execute after counting.

editText.addTextChangedListener(
new TextWatcher() {
    @Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
    @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

    private Timer timer=new Timer();
    private final long DELAY = 1000; // milliseconds

    @Override
    public void afterTextChanged(final Editable s) {
        timer.cancel();
        timer = new Timer();
        timer.schedule(
            new TimerTask() {
                @Override
                public void run() {
                      // adapter.getCount()  will give you the correct item counts 
                      Log.d(TAG, "run: afterTextChanged " + adapter.getCount());
                }
            }, 
            DELAY
        );
    }
}
);

      

Edited: September 5, 2019

You can also get the number of items using setup registerDataSetObserver

.

adapter.registerDataSetObserver(new DataSetObserver() {
    @Override
    public void onChanged() {
        super.onChanged();
        Log.d(TAG, "onChanged: " + adapter.getCount());
    }
});

      

thus it onChanged()

will be called every time the text changes. But if the offer list becomes empty, it will not be called.

0


source







All Articles