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.
source to share
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.
source to share
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());
}
});
source to share
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.
source to share
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");
}
}
source to share
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.
source to share