Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get filtered array size in AutoCompleteTextview

I am working on the project in which user can search data. For that, I have implemented AutoCompleteTextView.

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

Text change listener is as below:

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 my initial array size is 100 and if the user types 'a', then I want to get the size of filtered array.

Note: I have tried autoComplete.getAdapter().getCount(); but it gives the actual result after adding one more character.

like image 312
Ronak Thakkar Avatar asked Feb 05 '23 06:02

Ronak Thakkar


2 Answers

You cannot get correct filtered items' count in TextWatcher, because filtering usually takes longer time than TextWatcher event listeners. Therefore you get incorrect autoComplete.getAdapter().getCount() in afterTextChanged(). I would recommend to use custom listener which will be called every time when filtered items are changed.

I will provide 2 similar approaches: using separate classes and using only 1 class.

APPROACH 1: Your adapter should look like:

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();
                }
            }
        }
    };
}

An interface which is used to inform you when filtering will be finished:

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() method will be called when you enter something to an autocomplete input field and it gets filtered.

UPDATE (Trie Search):

I have created a Github project with a simple example of using Trie search algorithm to increase autocomplete performance very much.

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

like image 103
Ayaz Alifov Avatar answered Feb 06 '23 20:02

Ayaz Alifov


according to Ayaz Alifov answer you cannot get correct filtered items' count in TextWatcher, because filtering usually takes longer time than TextWatcher event listeners.

but i have done a trick with a timerTask. so the TextWatcher would 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's counts 
                      Log.d(TAG, "run: afterTextChanged " + adapter.getCount());
                }
            }, 
            DELAY
        );
    }
}
);

Edited: 5/Sep/2019

you can also get items count with the help of setting a registerDataSetObserver.

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

in this way the onChanged() will call every time text change. But if the suggestion list becomes empty, it will not be called.

like image 42
e.hadid Avatar answered Feb 06 '23 19:02

e.hadid