For my AutoCompleteTextView I need to fetch the data from a webservice. As it can take a little time I do not want UI thread to be not responsive, so I need somehow to fetch the data in a separate thread. For example, while fetching data from SQLite DB, it is very easy done with CursorAdapter method - runQueryOnBackgroundThread. I was looking around to other adapters like ArrayAdapter, BaseAdapter, but could not find anything similar...
Is there an easy way how to achieve this? I cannot simply use ArrayAdapter directly, as the suggestions list is dynamic - I always fetch the suggestions list depending on user input, so it cannot be pre-fetched and cached for further use...
If someone could give some tips or examples on this topic - would be great!
With the approach above, i also had those problems when typing very fast. I guess it´s because the filtering of the results is done asynchronously by the filter class, so there can be problems when modifying the ArrayList of the Adapter in the ui thread while filtering is done.
http://developer.android.com/reference/android/widget/Filter.html
However with following approach everything worked fine.
public class MyActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyAdapter myAdapter = new MyAdapter(this, android.R.layout.simple_dropdown_item_1line);
AutoCompleteTextView acTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
acTextView.setAdapter(myAdapter);
}
}
public class MyAdapter extends ArrayAdapter<MyObject> {
private Filter mFilter;
private List<MyObject> mSubData = new ArrayList<MyObject>();
static int counter=0;
public MyAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
setNotifyOnChange(false);
mFilter = new Filter() {
private int c = ++counter;
private List<MyObject> mData = new ArrayList<MyObject>();
#Override
protected FilterResults performFiltering(CharSequence constraint) {
// This method is called in a worker thread
mData.clear();
FilterResults filterResults = new FilterResults();
if(constraint != null) {
try {
// Here is the method (synchronous) that fetches the data
// from the server
URL url = new URL("...");
URLConnection conn = url.openConnection();
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
while ((line = rd.readLine()) != null) {
mData.add(new MyObject(line));
}
}
catch(Exception e) {
}
filterResults.values = mData;
filterResults.count = mData.size();
}
return filterResults;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence contraint, FilterResults results) {
if(c == counter) {
mSubData.clear();
if(results != null && results.count > 0) {
ArrayList<MyObject> objects = (ArrayList<MyObject>)results.values;
for (MyObject v : objects)
mSubData.add(v);
notifyDataSetChanged();
}
else {
notifyDataSetInvalidated();
}
}
}
};
}
#Override
public int getCount() {
return mSubData.size();
}
#Override
public MyObject getItem(int index) {
return mSubData.get(index);
}
#Override
public Filter getFilter() {
return mFilter;
}
}
EDITED: Added naive way to avoid the dropdown showing when you click a suggestion.
I do something like this in my app:
private AutoCompleteTextView mSearchbar;
private ArrayAdapter<String> mAutoCompleteAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
mAutoCompleteAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line);
mSearchbar = (AutoCompleteTextView) findViewById(R.id.searchbar);
mSearchbar.setThreshold(3);
mSearchbar.setAdapter(mAutoCompleteAdapter);
mSearchbar.addTextChangedListener(new TextWatcher() {
private boolean shouldAutoComplete = true;
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
shouldAutoComplete = true;
for (int position = 0; position < mAutoCompleteAdapter.getCount(); position++) {
if (mAutoCompleteAdapter.getItem(position).equalsIgnoreCase(s.toString())) {
shouldAutoComplete = false;
break;
}
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
if (shouldAutoComplete) {
new DoAutoCompleteSearch().execute(s.toString());
}
}
}
}
private class DoAutoCompleteSearch extends AsyncTask<String, Void, ArrayList<String>> {
#Override
protected ArrayList<String> doInBackground(String... params) {
ArrayList<String> autoComplete = new ArrayList<String>();
//do autocomplete search and stuff.
return autoComplete;
}
#Override
protected void onPostExecute(ArrayList<String> result) {
mAutoCompleteAdapter.clear();
for (String s : result)
mAutoCompleteAdapter.add(s);
}
}
had the same solution except that the problem is that everything is just fine ( variables are updated when i debug) but the autocomplete fills weirdly as in
when i type sco it has the results but does not show in list
but when i backspace it shows the result for sco. In debug all the variables are updated which only tells me that the UI is not getting updated for AutoCompleteTextView. as when i backspace it is triggered for update and then it shows earlier computer list then it(in the mean time it updates it with the new list items for new search string.
anyone ran into this problem?
Related
I implemented an AutoCompleteTextView where the data is updated from the server every time the user enters a new text input. It works well. However, every time I enter a new query, the previous results are displayed until the new result set is updated.
My guess is that it displays the currently queried results until the backend responds with the new search results. Is there a way to clear the current results?
Activity code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_test);
actv = (AutoCompleteTextView) findViewById(R.id.actv);
actv.setThreshold(1);
actv.setAdapter(new SearchSuggestionsAdapter(this, actv.getText().toString()));
Custom adapter:
public class SearchSuggestionsAdapter extends ArrayAdapter<String> {
List<String> types = new ArrayList<>();
protected static final String TAG = "SuggestionAdapter";
private List<String> suggestions;
private Context context;
public SearchSuggestionsAdapter(Activity context, String nameFilter) {
super(context, android.R.layout.simple_dropdown_item_1line);
suggestions = new ArrayList<String>();
this.context = context;
}
#Override
public int getCount() {
return suggestions.size();
}
#Override
public String getItem(int index) {
return suggestions.get(index);
}
#Override
public Filter getFilter() {
Filter myFilter = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
if (constraint != null) {
//Get new results from backend
//searchItemsFromServer is the method that returns the data
//new data is successfully sent. no problem there
List<String> new_suggestions = searchItemsFromServer(constraint.toString());
suggestions.clear();
for (int i = 0; i < new_suggestions.size(); i++) {
suggestions.add(new_suggestions.get(i));
}
filterResults.values = suggestions;
filterResults.count = suggestions.size();
}
return filterResults;
}
#Override
protected void publishResults(CharSequence contraint,
FilterResults results) {
if (results != null && results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
};
return myFilter;
}
}
Thank you in advance!
Change your code to your activity to this
SearchSuggestionsAdapter searchSuggestionsAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_test);
searchSuggestionsAdapter = new SearchSuggestionsAdapter(this, actv.getText().toString());
actv = (AutoCompleteTextView) findViewById(R.id.actv);
actv.setThreshold(1);
actv.setAdapter(searchSuggestionsAdapter);
actv.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(!hasFocus){
searchSuggestionsAdapter.clear();
}
}
});
}
Add the following code to your SearchSuggestionsAdapter
/**
* Create list and notify recycler view
*/
public void clear() {
if (suggestions != null) {
suggestions.clear();
notifyDataSetChanged();
}
}
actv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
suggestionAdapter.clear;
}
});
When user click on autocomplete remove the old adapter. But for this you need to define suggesstionAdapter first. and every time update this adapter and set it to autocomplete
Clear suggestion before filtering. Call adapter.clear();
Add new method in adapter
public void clear() {
suggestions.clear();
notifyDataSetChanged();
}
I have custom listview using simple adapter, Currently I have issue regarding filter that I have custom list data with numbers and characters in listview.
If I enter name then its give one blank space the filter results gets disappear.
I have list data like name then number for example : NAME 123, Whenever I enter name then gives space in that edit text then results are gone and list-view gets disappears.
I have tried this on below link but they used Array adapter, So my question is is it possible only in Array adapter or I can used simple adapter?
Android listview edittext filter space button?
If yes then how can I implement, kindly help. Advance thank you.
try this way
searchView.setOnEditorActionListener(new EditText.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
return true;
}
});
addTextChangeListener();
now create method addTextChangeListener
private void addTextChangeListener() {
searchView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence query, int start, int before, int count) {
query = query.toString().trim().toLowerCase();
final ArrayList<CityDataModel> filteredList = new ArrayList<>();
final CharSequence finalQuery = query;
new Thread(new Runnable() {
#Override
public void run() {
// Clear the filter list
filteredList.clear();
// If there is no search value, then add all original list items to filter list
if (TextUtils.isEmpty(finalQuery)) {
filteredList.addAll(cities);
} else {
// Iterate in the original List and add it to filter list...
for (CityDataModel item : cities) {
if (item.getCity_name().toLowerCase().contains(finalQuery.toString().toLowerCase())
) {
// Adding Matched items
filteredList.add(item);
}
}
}
// Set on UI Thread
((Activity) context).runOnUiThread(new Runnable() {
#Override
public void run() {
// Notify the List that the DataSet has changed...
adapter = new SearchCityAdapter(SearchCityClass.this, filteredList);
recyclerSearchCity.setAdapter(adapter);
}
});
}
}).start();
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
You can use any Adapter, you can just implements your adapter with android.widget.Filterable
Example Adapter,
public class AppAdapter extends RecyclerView.Adapter<AppHolder> implements Filterable {
public static final String TAG = AppAdapter.class.getSimpleName();
private ArrayList<App> mApps = new ArrayList<>();
private List<App> mCurrentItmCopy = new ArrayList<>();
private String currentFilter;
private MyArrayFilter mFilter;
#Override
public AppHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View receiverView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.layout_row_apps, parent, false);
return new AppHolder(receiverView);
}
#Override
public void onBindViewHolder(final AppHolder holder, int position) {
final App data = mApps.get(position);
}
#Override
public int getItemCount() {
return mApps.size();
}
#Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new MyArrayFilter();
}
return mFilter;
}
private class MyArrayFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mCurrentItmCopy == null || (mCurrentItmCopy.size() == 0)) {
mCurrentItmCopy = new ArrayList<App>(mApps);
}
ArrayList<App> newValues = new ArrayList<App>();
if (prefix != null && !TextUtils.isEmpty(prefix.toString())) {
String prefixString = prefix.toString().toLowerCase();
for (App value : mCurrentItmCopy) {
String label = value.getLabel().toLowerCase();
if ((label.contains(prefixString)) && !newValues.contains(value)) {
newValues.add(value);
}
}
results.values = newValues;
results.count = newValues.size();
} else {
results.values = new ArrayList<App>(mCurrentItmCopy);
results.count = mCurrentItmCopy.size();
mCurrentItmCopy.clear();
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
currentFilter = constraint.toString();
if (results.count > 0) {
mApps.clear();
addAll((ArrayList<App>) results.values);
} else {
mApps.clear();
notifyDataSetChanged();
}
}
}
public void addAll(List<App> items) {
if (items != null) {
mApps.addAll(items);
}
notifyDataSetChanged();
}
}
In the above Adapter instead of App, you can use your object.
You can call from your activity or fragment like this,
mAdapter.getFilter().filter(newText);
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.
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
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.
Basically, we have to implement Filterable at 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 and TextWatcher
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());
}
});
I am assuming that you have gone through the basic search options available in android/java and you are not satisfied with the results.
If you do not want to go through entire list at every text change, the only way is to implement a datastructure which does that.
The obvious solution will be trie.read this to get an idea about trie
Now, this works on the concept of pre-processing the data before searching. Since you have limited elements - it will not take much time, and you can possibly do it when the page loads.
Steps -
- Process and index all elements on load. Put indexes on a k-ary tree (it will be 32-ary, every character will be an alphabet).
- on text changed - traverse to the node and get the count. It will take O(1).
I believe this is the fastest you can go.
The above will work best if you have words indexed or if you just have to do startswith.
Sa Qada's answer is a very good approach, However, my below answer gave me better 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) {
}
});
Method for Filter:
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");
}
}
I try added filter to ArrayList (data is from json/php script on my server). I find but I not see resolving...
I try more sample codes and more resolving, but nothing works.
listView = (ListView) findViewById(R.id.list);
list = new ArrayList<FriendItem>();
adapter = new FriendAllAdapter(context, list);
listView.setAdapter(adapter);
String filename = getResources().getString(R.string.friendalllist_php);
asyncLoadVolley = new AsyncLoadVolley(context, filename);
Map<String, String> map = new HashMap<String, String>();
map.put(Constant.ID, Sessions.getUserId(context));
asyncLoadVolley.setBasicNameValuePair(map);
asyncLoadVolley.setOnAsyncTaskListener(asyncTaskListener);
connectionDetector = new ConnectionDetector(context);
if(savedInstanceState==null) {
}
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(listItemClickListener);
EditText myFilter = (EditText) findViewById(R.id.myFilter);
myFilter.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
//here added filter...
}
});
For example:
You have a model - FriendItem
public class FriendItem {
private String firstName;
private String lastName;
//.....
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
and have an adapter - FriendAllAdapter.
In this adapter you should implement Filterable interface.
You"ll need to Override getFilter() method and return new object of Filter class (android.widget.Filter).
In performFiltering() method your need return FilterResults object with count of filtered elements and value - list of elements. Also in this method you need to realize the algorithm comparison of the search query to values in your FriendItem (list item). In my example I search FriendItem(s) which fields (firstName or lastName) contains text in my editText.
Input parameter is a CharSequence object which you set in onTextChanged() method on added textChangeListener to your EditText.
myFilter.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
}
});
In publishResults() method your get filtering result your need cast results.values to List and replace your list to filtering result.
public class FriendAllAdapter extends BaseAdapter implements Filterable {
private List<FriendItem> list;
private final List<FriendItem> fullList = new ArrayList<>();
public FriendAllAdapter(Context context, List<FriendItem> list) {
//...... your code
this.list = list;
fullList.addAll(list);
}
//...... your code
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//... your code
}
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint == null || constraint.length() == 0) { // if your editText field is empty, return full list of FriendItem
results.count = fullList.size();
results.values = fullList;
} else {
List<FriendItem> filteredList = new ArrayList<>();
constraint = constraint.toString().toLowerCase(); // if we ignore case
for (FriendItem item : fullList) {
String firstName = item.getFirstName().toLowerCase(); // if we ignore case
String lastName = item.getLastName().toLowerCase(); // if we ignore case
if (firstName.contains(constraint.toString()) || lastName.contains(constraint.toString())) {
filteredList.add(item); // added item witch contains our text in EditText
}
}
results.count = filteredList.size(); // set count of filtered list
results.values = filteredList; // set filtered list
}
return results; // return our filtered list
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<FriendItem>) results.values; // replace list to filtered list
notifyDataSetChanged(); // refresh adapter
}
};
return filter;
}
}
I think you`ll understand it.
P.S. sorry for my English.
I have a listView with a custom adapter.
I have an edit text view in my activity where the user can type to filter the content on the list view.
Lets say I have two items on the list view. After doing the filter, the ArrayList is reduced to one element.
The problem is that the getView method is still executed for the two previous rows.
Here is my code.
ListActivity:
public class TeamsListActivity extends ListActivity {
private ArrayList<Team> teams = new ArrayList<Team>();
private TeamsListAdapter teamsListAdapter = null;
private EditText searchInput = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.teams_list);
// get association id from the intent.
int afId = getIntent().getExtras().getInt("afId");
Log.i(MainActivity.TAG, "id_association =" + afId);
// get the teams from the association.
this.teams = TeamsDAO.getInstance(getApplicationContext())
.getTeamsByAssociation(afId);
// inits the list adapter
teamsListAdapter = new TeamsListAdapter(this, teams);
setListAdapter(teamsListAdapter);
// gets the search input view
searchInput = (EditText) findViewById(R.id.teams_list_search_box);
searchInput.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void afterTextChanged(Editable s) {
Log.d(Constants.TAG,"Teams search: " + s.toString());
teamsListAdapter.getFilter().filter(s);
}
});
}
}
My adapter
public class TeamsListAdapter extends ArrayAdapter<Team> implements Filterable {
private ArrayList<Team> teams = null;
private Context context = null;
public TeamsListAdapter(Context context,
ArrayList<Team> objects) {
super(context, R.layout.teams_list_row, objects);
this.context = context;
this.teams = objects;
}
#Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.teams_list_row, null);
}
Team t = teams.get(position);
if (t != null) {
TextView txtTeamName = (TextView) v.findViewById(R.id.teams_list_team_name);
txtTeamName.setText(t.getName());
ImageView ivTeamLogo = (ImageView) v.findViewById(R.id.teams_list_team_logo);
ivTeamLogo.setImageResource(R.drawable.af_braga);
}
return v;
}
#Override
public Filter getFilter() {
return new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Log.d(Constants.TAG, "**** Search RESULTS for: " + constraint);
teams = (ArrayList<Team>) results.values;
Log.d(Constants.TAG,"size:"+teams.size());
TeamsListAdapter.this.notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
Log.d(Constants.TAG, "**** PERFORM TEAK FILTERING for: " + constraint);
ArrayList<Team> filteredResults = getFilteredResults(constraint);
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
/**
* filters the teamsList
* #param constraint String The text to search
* #return ArrayList<Team>
*/
private ArrayList<Team> getFilteredResults(CharSequence constraint) {
ArrayList<Team> teams = TeamsListAdapter.this.teams;
ArrayList<Team> filteredTeams = new ArrayList<Team>();
for(int i=0;i< teams.size();i++){
if(teams.get(i).getName().toLowerCase().startsWith(constraint.toString().toLowerCase())){
filteredTeams.add(teams.get(i));
}
}
return filteredTeams;
}
};
}
}
Clarification of the problem.
1.Let´s say my list starts showing two elements.
2. The user inserts some text in the edit text to filter the list content which results in a updated arrayList with only one element who matches the query.
3. After called the notifyDataSetChanged the getView still is called like there was two rows in the dataset resulting in IndexOutOfBoundsException when executing Team t = teams.get(position) becuase the arrayList has only one element now so when position = 1 => the get(postion) fails.
Hope its clear now.
Well I fix my problem.
I needed to override a couple of methods in my adapter like this.
#Override
public int getCount() {
return teams.size();
}
#Override
public Team getItem(int position) {
return teams.get(position);
}
#Override
public long getItemId(int position) {
return teams.get(position).getId();
}
Now i have discoreved a problem with my search implementation. Because i am updating the original dataset the search only works one time. If I try to go back to original list i cant because my original data array has been modified. Easy to solve with with a database query instead of filtering the array list.
I think you dont need
teams = (ArrayList) results.values;
in publishResults function.