Hi everyone I have an adapter which extends the ArrayAdapter class and overrides some Filterable methods. I am using this Adapter to perform some filtering while the user types inside an AutocompleteTextView. But I saw that if you type a bit fast the updanting of the filtered items becomes very slow. This is the adapter class:
public class MunicipalitySearchAdapter extends ArrayAdapter<Municipality> {
private ArrayList<Municipality> municipalities;
private ArrayList<Municipality> allMunicipalities;
private ArrayList<Municipality> suggestedMunicipalities;
private int viewResourceId;
#SuppressWarnings("unchecked")
public MunicipalitySearchAdapter(Context context, int viewResourceId, ArrayList<Municipality> municipalities) {
super(context, viewResourceId, municipalities);
this.municipalities = municipalities;
this.allMunicipalities = (ArrayList<Municipality>) this.municipalities.clone();
this.suggestedMunicipalities = new ArrayList<Municipality>();
this.viewResourceId = viewResourceId;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(this.viewResourceId, null);
}
Municipality municipality = municipalities.get(position);
if (municipality != null) {
TextView munNameTxtView = (TextView) v.findViewById(R.id.name);
TextView proSignTxtView = (TextView) v.findViewById(R.id.sign);
if (munNameTxtView != null) {
munNameTxtView.setText(municipality.getName());
}
if (proSignTxtView != null) {
proSignTxtView.setText(municipality.getProvinceSign());
}
}
return v;
}
#Override
public Filter getFilter() {
return municipalityFilter;
}
Filter municipalityFilter = new Filter() {
#Override
public String convertResultToString(Object resultValue) {
String str = ((Municipality) (resultValue)).getName();
return str;
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
suggestedMunicipalities.clear();
for (Municipality municipality : allMunicipalities) {
if (municipality.getName().toLowerCase(Locale.getDefault()).startsWith(constraint.toString().toLowerCase(Locale.getDefault()))) {
suggestedMunicipalities.add(municipality);
}
}
FilterResults filterRes = new FilterResults();
filterRes.values = suggestedMunicipalities;
filterRes.count = suggestedMunicipalities.size();
return filterRes;
}
else {
return new FilterResults();
}
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
#SuppressWarnings("unchecked")
ArrayList<Municipality> filteredMunicipalities = (ArrayList<Municipality>) results.values;
ArrayList<Municipality> supportMunicipalitiesList = new ArrayList<Municipality>();
clear();
for (Municipality mun : filteredMunicipalities) {
supportMunicipalitiesList.add(mun);
}
Iterator<Municipality> municipalityIterator = supportMunicipalitiesList.iterator();
while (municipalityIterator.hasNext()) {
Municipality municipality = municipalityIterator.next();
add(municipality);
}
notifyDataSetChanged();
}
}
};
}
I would like to ask if someone knows how to increase the performance of this kind of AutocompleteTextView and make the updating a faster. What should I do? Thanks!
EDIT: I have createed this classes:
Vertex:
public class Vertex {
private HashMap<Character, Vertex> vertexSons;
private List<Integer> wordsIndexList;
private List<Integer> prefixesIndexList;
private int wordsNumber;
private int prefixesNumber;
public Vertex() {
vertexSons = new HashMap<Character, Vertex>();
wordsIndexList = new ArrayList<Integer>();
prefixesIndexList = new ArrayList<Integer>();
wordsNumber = 0;
prefixesNumber = 0;
}
public boolean hasWords() {
if (wordsNumber > 0) {
return true;
}
return false;
}
public boolean hasPrefixes() {
if (prefixesNumber > 0) {
return true;
}
return false;
}
public void addVertexSon(Character character) {
vertexSons.put(character, new Vertex());
}
public void addIndexToWordsIndexList(int index) {
wordsIndexList.add(index);
}
public void addIndexToPrefixesIndexList(int index) {
prefixesIndexList.add(index);
}
public HashMap<Character, Vertex> getVertexSons() {
return vertexSons;
}
public List<Integer> getWordsIndexList() {
return wordsIndexList;
}
public List<Integer> getPrefixesIndexList() {
return prefixesIndexList;
}
public int getWordsNumber() {
return wordsNumber;
}
public int getPrefixesNumber() {
return prefixesNumber;
}
public void increaseWordsNumber() {
wordsNumber++;
}
public void increasePrefixesNumber() {
prefixesNumber++;
}
}
And Trie:
public class Trie {
private Vertex rootVertex;
public Trie(List<Trieable> objectList, Locale locale) {
rootVertex = new Vertex();
for (int i = 0; i<objectList.size(); i++) {
String word = objectList.get(i).toString().toLowerCase(locale);
addWord(rootVertex, word, i);
}
}
public Vertex getRootVertex() {
return rootVertex;
}
public void addWord(Vertex vertex, String word, int index) {
if (word.isEmpty()) {
vertex.addIndexToWordsIndexList(index);
vertex.increaseWordsNumber();
}
else {
vertex.addIndexToPrefixesIndexList(index);
vertex.increasePrefixesNumber();
Character fChar = word.charAt(0);
HashMap<Character, Vertex> vertexSons = vertex.getVertexSons();
if (!vertexSons.containsKey(fChar)) {
vertex.addVertexSon(fChar);
}
word = (word.length() == 1) ? "" : word.substring(1);
addWord(vertexSons.get(fChar), word, index);
}
}
public List<Integer> getWordsIndexes(Vertex vertex, String word) {
if (word.isEmpty()) {
return vertex.getWordsIndexList();
}
else {
Character fChar = word.charAt(0);
if (!(vertex.getVertexSons().containsKey(fChar))) {
return null;
}
else {
word = (word.length() == 1) ? "" : word.substring(1);
return getWordsIndexes(vertex.getVertexSons().get(fChar), word);
}
}
}
public List<Integer> getPrefixesIndexes(Vertex vertex, String prefix) {
if (prefix.isEmpty()) {
return vertex.getWordsIndexList();
}
else {
Character fChar = prefix.charAt(0);
if (!(vertex.getVertexSons().containsKey(fChar))) {
return null;
}
else {
prefix = (prefix.length() == 1) ? "" : prefix.substring(1);
return getWordsIndexes(vertex.getVertexSons().get(fChar), prefix);
}
}
}
}
And edited my Filter like this:
Filter municipalityFilter = new Filter() {
#Override
public String convertResultToString(Object resultValue) {
String str = ((Municipality) (resultValue)).getName();
return str;
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
String constraintString = constraint.toString().trim();
suggestedMunicipalities.clear();
List<Integer> wordsIndexesList = municipalityTrie.getWordsIndexes(municipalityTrie.getRootVertex(), constraintString);
for (int index : wordsIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
List<Integer> prefixesIndexesList = municipalityTrie.getPrefixesIndexes(municipalityTrie.getRootVertex(), constraintString);
for (int index : prefixesIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
FilterResults filterRes = new FilterResults();
filterRes.values = suggestedMunicipalities;
filterRes.count = suggestedMunicipalities.size();
return filterRes;
}
else {
return new FilterResults();
}
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
#SuppressWarnings("unchecked")
ArrayList<Municipality> filteredMunicipalities = (ArrayList<Municipality>) results.values;
ArrayList<Municipality> supportMunicipalitiesList = new ArrayList<Municipality>();
clear();
for (Municipality mun : filteredMunicipalities) {
supportMunicipalitiesList.add(mun);
}
Iterator<Municipality> municipalityIterator = supportMunicipalitiesList.iterator();
while (municipalityIterator.hasNext()) {
Municipality municipality = municipalityIterator.next();
add(municipality);
}
notifyDataSetChanged();
}
}
};
Now I get a null pointer warning when I type in the AutoCompleteTextView at this line:
List<Integer> wordsIndexesList = municipalityTrie.getWordsIndexes(municipalityTrie.getRootVertex(), constraintString);
for (int index : wordsIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
In the for (int index : wordsIndexesList) statement. What should I do? Thanks!
You should look into using a trie, it would be perfect for auto complete.
Here is what one looks like:
As you get more characters, you can traverse further down the tree, and the number of possible options will get smaller and smaller.
This would be significantly faster than looking over your entire list each time.
Edit: After reflecting on my answer I think a much easier solution would be to just use any kind of sorted map. Checkout this answer for an example.
Related
I'm working with Checkboxes and a Spinner in which you can select how to filter your Checkboxes. You've got 3 options:
1 - "All" aka "Alle" -> Show all Checkboxes (Checked & Unchecked)
2 - "Checked" aka "Vorhanden" -> Show all checked Checkboxes
3 - "Unchecked" aka "Nicht vrhd." -> Show all unchecked Checkboxes
Let me show the necessary information in my Adapter, I'm working on, first:
SchrankAdapter:
public SchrankAdapter(Context context, ArrayList<CategoryName> categoryTopic, Map<String, String> dropDownValues, RelativeLayout parentLayout){
this.context = context;
this.categoryTopic = categoryTopic;
this.uncheckedValues.addAll(categoryTopic);
this.dropDownValues = dropDownValues;
this.parentLayout = parentLayout;
this.allValues.addAll(categoryTopic);
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_schrank, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
CategoryName parentItem = categoryTopic.get(position);
holder.categoryName.setText(parentItem.getCategoryName());
setCatItemRecycler(holder.childRecView, parentItem.getChildViewList(), parentItem.getCategoryName());
}
private void setCatItemRecycler(RecyclerView recyclerView, ArrayList<CategoryChild> categoryChildrenList, String categoryName) {
childAdapter = new SchrankChildAdapter(context, categoryChildrenList, categoryName, this, parentLayout);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.setAdapter(childAdapter);
}
public Filter getSpinnerFilter() {
return spinnerFilter;
}
private Filter spinnerFilter = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<CategoryName> filteredList = new ArrayList<>();
if(constraint == null || constraint.length() == 0 || constraint.equals("all")) {
filteredList.addAll(allValues);
spinnerValue = "all";
} else if(constraint.equals("checked")) {
filteredList.addAll(checkedValues);
spinnerValue = "checked";
} else if(constraint.equals("unchecked")) {
filteredList.addAll(uncheckedValues);
spinnerValue = "unchecked";
}
FilterResults results = new FilterResults();
if(filteredList != null)
results.values = sortEverything(filteredList);
else
results.values = allValues;
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
categoryTopic.clear();
categoryTopic.addAll((ArrayList) results.values);
notifyDataSetChanged();
}
};
public class ViewHolder extends RecyclerView.ViewHolder{
TextView categoryName;
RelativeLayout childView;
RecyclerView childRecView;
Button btFolderPlus, btFolderMinus;
Spinner dropDownMenu;
public ViewHolder(#NonNull View itemView) {
super(itemView);
categoryName = itemView.findViewById(R.id.categoryName);
childView = itemView.findViewById(R.id.childView);
childRecView = itemView.findViewById(R.id.childRecView);
btFolderPlus = itemView.findViewById(R.id.btFolderPlus);
btFolderMinus = itemView.findViewById(R.id.btFolderMinus);
dropDownMenu = itemView.findViewById(R.id.dropDown);
}
}
And this 2 methods are called on each checkbox, which is getting checked or unchecked:
#Override
public void onCheckboxChangeTrue(CategoryChild childItem, String categoryName) {
boolean containsName = false;
if(this.checkedValues != null && !this.checkedValues.isEmpty()) {
for(CategoryName checkedCategory: this.checkedValues) {
if(checkedCategory.getCategoryName().equals(categoryName)) {
if(!checkedCategory.getChildViewList().contains(childItem)) {
checkedCategory.getChildViewList().add(childItem);
}
containsName = true;
}
}
}
if(!containsName) {
ArrayList<CategoryChild> childList = new ArrayList<>();
childList.add(childItem);
this.checkedValues.add(new CategoryName(categoryName, childList));
}
if(!this.uncheckedValues.isEmpty() && this.uncheckedValues != null) {
for(CategoryName uncheckedCategory: this.uncheckedValues) {
if(uncheckedCategory.getCategoryName().equals(categoryName)) {
if(uncheckedCategory.getChildViewList().contains(childItem)) {
uncheckedCategory.getChildViewList().remove(childItem);
break;
}
}
}
} else {
this.uncheckedValues.clear();
}
}
#Override
public void onCheckboxChangeFalse(CategoryChild childItem, String categoryName) {
boolean containsName = false;
if(!this.checkedValues.isEmpty() && this.checkedValues != null) {
for (CategoryName checkedCategory : this.checkedValues) {
if (checkedCategory.getCategoryName().equals(categoryName)) {
if (checkedCategory.getChildViewList().contains(childItem)) {
checkedCategory.getChildViewList().remove(childItem);
break;
}
}
}
}
else {
this.checkedValues.clear();
}
for(CategoryName uncheckedCategory: this.uncheckedValues) {
if(uncheckedCategory.getCategoryName().equals(categoryName)) {
if(!uncheckedCategory.getChildViewList().contains(childItem)) {
uncheckedCategory.getChildViewList().add(childItem);
}
containsName = true;
}
}
if(!containsName) {
ArrayList<CategoryChild> childList = new ArrayList<>();
childList.add(childItem);
this.uncheckedValues.add(new CategoryName(categoryName, childList));
}
}
Here comes the problem:
Everytime I check a checkbox, it gets added to "checkedValues" which is fine, but it gets removed from "uncheckedValues" AND "allValues" even though, allValues should stay the same.
And I don't even call the remove() method on the "allValues" Arraylist, only on "uncheckedValues" and "checkedValues". So how is it possible, that elements of "allValues" get removed as well?
Thanks in advance!
It's because both Arraylists are related to "categoryTopic" Arraylist. That's why it changes both Arraylists even though you call the remove() function on "uncheckedValues" and not "allValues"
I am using search filtered list. The list contains accented characters.
If I type Cam, it should support and accept Càm but it's not working. I am clueless where exactly I need to give to work in Adapter class.
Here is the code.
public class MainActivity extends AppCompatActivity {
private HighlightArrayAdapter mHighlightArrayAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Listview sample data
String products[] = {"Càmdoón", "córean", "Lamià", "dell", "HTC One X", "HTC Wildfire S", "HTC Sense", "HTC Sensàtion XE",
"iPhone 4S", "Samsóng Galàxy Note 800",
"Samsung Galàxy S3", "MacBook Air", "Màc Mini", "MàcBook Pro"};
ListView listView = (ListView) findViewById(R.id.listview);
EditText editText = (EditText) findViewById(R.id.inputSearch);
// Adding items to listview
mHighlightArrayAdapter = new HighlightArrayAdapter(this, R.layout.list_item, R.id.product_name, products);
listView.setAdapter(mHighlightArrayAdapter);
// Enabling Search Filter
editText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
mHighlightArrayAdapter.getFilter().filter(cs);
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
int arg3) {
}
#Override
public void afterTextChanged(Editable arg0) {
}
});
}
}
//HighlightArrayAdapter.
public class HighlightArrayAdapter extends ArrayAdapter<String> {
private final LayoutInflater mInflater;
private final Context mContext;
private final int mResource;
private List<String> mObjects;
private int mFieldId = 0;
private ArrayList<String> mOriginalValues;
private ArrayFilter mFilter;
private final Object mLock = new Object();
private String mSearchText; // this var for highlight
Pattern mPattern;
public HighlightArrayAdapter(Context context, int resource, int textViewResourceId, String[] objects) {
super(context, resource, textViewResourceId, objects);
mContext = context;
mInflater = LayoutInflater.from(context);
mResource = resource;
mObjects = Arrays.asList(objects);
mFieldId = textViewResourceId;
}
#Override
public Context getContext() {
return mContext;
}
#Override
public int getCount() {
return mObjects.size();
}
#Override
public String getItem(int position) {
return mObjects.get(position);
}
#Override
public int getPosition(String item) {
return mObjects.indexOf(item);
}
#Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}
private class ArrayFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mOriginalValues == null) {
synchronized (mLock) {
mOriginalValues = new ArrayList<>(mObjects);
}
}
if (prefix == null || prefix.length() == 0) {
mSearchText = "";
ArrayList<String> list;
synchronized (mLock) {
list = new ArrayList<>(mOriginalValues);
}
results.values = list;
results.count = list.size();
} else {
String prefixString = prefix.toString().toLowerCase();
mSearchText = prefixString;
ArrayList<String> values;
synchronized (mLock) {
values = new ArrayList<>(mOriginalValues);
}
final int count = values.size();
final ArrayList<String> newValues = new ArrayList<>();
for (int i = 0; i < count; i++) {
final String value = values.get(i);
final String valueText = value.toLowerCase();
// First match against the whole, non-splitted value
if (valueText.startsWith(prefixString) || valueText.contains(prefixString)) {
newValues.add(value);
} else {
final String[] words = valueText.split(" ");
final int wordCount = words.length;
// Start at index 0, in case valueText starts with space(s)
for (int k = 0; k < wordCount; k++) {
if (words[k].startsWith(prefixString) || words[k].contains(prefixString)) {
newValues.add(value);
break;
}
}
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
mObjects = (List<String>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.inflate(mResource, parent, false);
} else {
view = convertView;
}
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
// HIGHLIGHT...
String fullText = getItem(position);
if (mSearchText != null && !mSearchText.isEmpty()) {
int startPos = fullText.toLowerCase(Locale.US).indexOf(mSearchText.toLowerCase(Locale.US));
int endPos = startPos + mSearchText.length();
if (startPos != -1) {
//Spannable spannable = new SpannableString(removeAccents(fullText)); // i used removeAccents but not worked.
Spannable spannable = new SpannableString(fullText);
ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setText(spannable);
} else {
text.setText(fullText);
}
} else {
text.setText(fullText);
}
return view;
}
/* public static String removeAccents(String text) {
return text == null ? null : Normalizer.normalize(text, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}*/
/*private SpannableStringBuilder createHighlightedString(String nodeText, int highlightColor) {
SpannableStringBuilder returnValue = new SpannableStringBuilder(nodeText);
String lowercaseNodeText = nodeText.toLowerCase();
Matcher matcher = mSearchText.matcher(lowercaseNodeText);
while (matcher.find()) {
returnValue.setSpan(new ForegroundColorSpan(highlightColor), matcher.start(0),
matcher.end(0), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
return returnValue;
}*/
}
Here is the screenshot.
Scenario 1: (This is working)
Scenario 2: ( This is not working when I type normal character of a):
Scenario 3: (This is working when I type accented character):
So how to make Scenario 2 to work when I give normal character search in word to support the accented character list to accept.
I used InCombiningDiacriticalMarks but it's not working i am clueless where exactly need to give.
Kindly help me please in adapter class.
You should match your filtered list to a diactritics-less String.
public static String removeDiacritics(String input) {
String out = "" + input;
out = out.replaceAll(" ", "");
out = out.replaceAll("[èéêë]", "e");
out = out.replaceAll("[ûù]", "u");
out = out.replaceAll("[ïî]", "i");
out = out.replaceAll("[àâ]", "a");
out = out.replaceAll("Ô", "o");
out = out.replaceAll("[ÈÉÊË]", "E");
out = out.replaceAll("[ÛÙ]", "U");
out = out.replaceAll("[ÏÎ]", "I");
out = out.replaceAll("[ÀÂ]", "A");
out = out.replaceAll("Ô", "O");
out = out.replaceAll("-", "");
return out;
}
This way you will not be matching "Cam" with "Càm" anymore, but "Cam" with "Cam". You should also transform your strings to lower (or upper) case to be Upper-case permissive.
hope it helps!
In this code I have search multiple text ex. I have search sachin.
sachin is show the list but click to back sachin to sac not display to sa list on adapter. How to solve it without any library?
when I press back space and remove a char this time dos not list not display.
Example
Suppose I have search sachin to text change sachin to sac. In this case not display sa list. I have clear all list that case display all list.
search sa show the list
sachin and sardar
now search sac
now display the sachin
search only sac to sc
that's time display only sachin
not disply the sardar in the list
My Adapter
private class ItemFilter extends Filter {
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null && constraint.length() > 0) {
ArrayList<Build> buildlist = new ArrayList<Build>();
for (int i = 0; i < buildList.size(); i++) {
if ((buildList.get(i).getName().toUpperCase())
.contains(constraint.toString().toUpperCase())) {
Build babydata = new Build(buildList.get(i).getImages(), buildList.get(i).getName());
buildlist.add(babydata);
}
}
results.count = buildlist.size();
results.values = buildlist;
} else {
results.count = buildList.size();
results.values = buildList;
}
return results;
}
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
buildList = (ArrayList<Build>) results.values;
notifyDataSetChanged();
}
}
Activity class
EditText editTxt = (EditText) findViewById(R.id.search);
editTxt.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() <= 0) {
Log.d("jay", "s.length() <= 0" + s.toString());
buildList.clear();
buildList.addAll(buildListCopy);
recyclerView.setAdapter(null);
buildCustomAdapter = new BuildCustomAdapter(buildList);
recyclerView.setAdapter(buildCustomAdapter);
} else {
buildCustomAdapter.getFilter().filter(s.toString());
}
Log.d("jay", "mobisharnam" + s.toString());
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
Without any Library simple logic
Finally I have solution in this question
In your Adapter Constructor add the new copy list
After the copy list is add in filter
private List<YourModel> modelList;
private List<YourModel> modelListCopy;
private ItemFilter mFilter = new ItemFilter();
public YourAdapter(List<YourModel> modelList) {
this.modelList= modelList;
this.modelListCopy= new ArrayList<>();
modelListCopy.addAll(modelList);
}
private class ItemFilter extends Filter {
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null && constraint.length() > 0) {
List<YourModel> filterList = new ArrayList<YourModel>();
for (int i = 0; i < modelListCopy.size(); i++) {
if ((modelListCopy.get(i).getName().toUpperCase())
.contains(constraint.toString().toUpperCase())) {
YourModel builddata = new YourModel(modelListCopy.get(i).getImages(), modelListCopy.get(i).getName());
filterList.add(builddata);
}
}
results.count = filterList.size();
results.values = filterList;
} else {
results.count = modelListCopy.size();
results.values = modelListCopy;
}
return results;
}
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
modelList= (ArrayList<YourModel>) results.values;
notifyDataSetChanged();
}
Activity Class
like this
CustomAdapter youradapter= new CustomAdapter(modelList);
I prefer Predicate for search filter
I have changed your code to use Predicate, You have to use google guava library for that
include following depedency in your gradle
compile 'com.google.guava:guava:19.0'
now, check following code
EditText editTxt = (EditText) findViewById(R.id.search);
editTxt.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String searchQuery = s.toString();
if (buildListCopy != null && buildListCopy.size() > 0) {
buildList = Lists.newArrayList(Collections2.filter(buildListCopy, new SearchFilter(searchQuery)));
recyclerView.setAdapter(null);
if (buildList != null && buildList.size() > 0) {);
buildCustomAdapter = new BuildCustomAdapter(buildList);
recyclerView.setAdapter(buildCustomAdapter);
recyclerView.getAdapter().notifyDataSetChanged();
}
}
return false;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
Here is your search filter predicate which we have used above
/**
* Custom Predicate class to filter list as per search query
*/
public final class SearchFilter implements Predicate<Build> {
private final Pattern pattern;
public SearchFilter(final String regex) {
pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.LITERAL);
}
#Override
public boolean apply(final Build input) {
return pattern.matcher(input.getName().toLowerCase()).find();
}
}
I'm populating a listview with data from a database, it includes Images as well as text. So I can't actually filter the data then pass it to the listview. I have to filter the listview it's self. I have populated the listview using a simple adapter and images load. The problem is when filtering the list view it crashes.(See logcat).
Code I'm using:
Custom Simple Adapter to handle the images
public class ExtendedSimpleAdapter extends SimpleAdapter{
List<HashMap<String, String>> map;
String[] from;
int layout;
int[] to;
Context context;
LayoutInflater mInflater;
public ExtendedSimpleAdapter(Context context, ArrayList<HashMap<String, String>> data,
int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
layout = resource;
map = data;
this.from = from;
this.to = to;
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return this.createViewFromResource(position, convertView, parent, layout);
}
private View createViewFromResource(int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
v = mInflater.inflate(resource, parent, false);
} else {
v = convertView;
}
this.bindView(position, v);
return v;
}
private void bindView(int position, View view) {
final Map dataSet = map.get(position);
if (dataSet == null) {
return;
}
final ViewBinder binder = super.getViewBinder();
final int count = to.length;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
if (text == null) {
text = "";
}
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, data, text);
}
if (!bound) {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() +
" should be bound to a Boolean, not a " +
(data == null ? "<unknown type>" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
setViewImage((ImageView) v, (Integer) data);
} else if (data instanceof String) {
setViewImage((ImageView) v, (String) data);
} else {
setViewImage((ImageView) v, text);
}
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleAdapter");
}
}
}
}
}
#Override
public int getCount() {
return super.getCount();
}
public void setViewImage(ImageView v, String bmp) {
v.setImageBitmap(ImageTools.decodeBase64(bmp));
}
}
Calling that class:
adapter = new ExtendedSimpleAdapter(
getActivity(), doctorsList,
R.layout.listview_item_layout_doctor,
new String[]{KEY_ID, KEY_NAME, KEY_SPECIALTY, KEY_RATING, KEY_ACCOUNT, KEY_ACTIVE, KEY_IMAGE},
new int[]{R.id.id, R.id.fname, R.id.Specialty, R.id.Rating, R.id.Account, R.id.Active, R.id.doctorImage});
Logcat:
java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at my.app.adapters.ExtendedSimpleAdapter.bindView(ExtendedSimpleAdapter.java:61)
at my.app.adapters.ExtendedSimpleAdapter.createViewFromResource(ExtendedSimpleAdapter.java:54)
at my.app.adapters.ExtendedSimpleAdapter.getView(ExtendedSimpleAdapter.java:41)
I fixed it by implementing a custom filter:
private class CustomFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mUnfilteredData == null) {
mUnfilteredData = new ArrayList<HashMap<String, String>>(map);
}
if (prefix == null || prefix.length() == 0) {
List<HashMap<String, String>> list = mUnfilteredData;
results.values = list;
results.count = list.size();
} else {
String prefixString = prefix.toString().toLowerCase();
List<HashMap<String, String>> unfilteredValues = mUnfilteredData;
int count = unfilteredValues.size();
ArrayList<Map<String, ?>> newValues = new ArrayList<Map<String, ?>>(count);
for (int i = 0; i < count; i++) {
Map<String, ?> h = unfilteredValues.get(i);
if (h != null) {
int len = to.length;
for (int j=0; j<len; j++) {
String str = (String)h.get(from[j]);
String[] words = str.split(" ");
int wordCount = words.length;
for (int k = 0; k < wordCount; k++) {
String word = words[k];
if (word.toLowerCase().contains(prefixString)) {
newValues.add(h);
break;
}
}
}
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
#SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
//Remove duplicates
map.addAll((java.util.Collection<? extends HashMap<String, String>>) results.values);
HashSet hs = new HashSet();
hs.addAll(map);
map.clear();
map.addAll(hs);
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
Add this to the adapter:
#Override
public Filter getFilter() {
if(filter != null) return filter;
else return filter = new CustomFilter();
}
I need to implement an AutocompleteTextView with a Trie search. I am using a Vertex class and a Trie class for the Trie tree data structure and an adapter for customize the view of the dropdown box of the autocompleteTextView.
Here are all the classes I am using:
The Vertex class:
public class Vertex {
private HashMap<Character, Vertex> vertexSons;
private List<Integer> wordsIndexList;
private List<Integer> prefixesIndexList;
private int wordsNumber;
private int prefixesNumber;
public Vertex() {
vertexSons = new HashMap<Character, Vertex>();
wordsIndexList = new ArrayList<Integer>();
prefixesIndexList = new ArrayList<Integer>();
wordsNumber = 0;
prefixesNumber = 0;
}
public boolean hasWords() {
if (wordsNumber > 0) {
return true;
}
return false;
}
public boolean hasPrefixes() {
if (prefixesNumber > 0) {
return true;
}
return false;
}
public void addVertexSon(Character character) {
vertexSons.put(character, new Vertex());
}
public void addIndexToWordsIndexList(int index) {
wordsIndexList.add(index);
}
public void addIndexToPrefixesIndexList(int index) {
prefixesIndexList.add(index);
}
public HashMap<Character, Vertex> getVertexSons() {
return vertexSons;
}
public List<Integer> getWordsIndexList() {
return wordsIndexList;
}
public List<Integer> getPrefixesIndexList() {
return prefixesIndexList;
}
public int getWordsNumber() {
return wordsNumber;
}
public int getPrefixesNumber() {
return prefixesNumber;
}
public void increaseWordsNumber() {
wordsNumber++;
}
public void increasePrefixesNumber() {
prefixesNumber++;
}
}
The Trie class:
public class Trie {
private Vertex rootVertex;
public Trie(List<Trieable> objectList, Locale locale) {
rootVertex = new Vertex();
for (int i = 0; i<objectList.size(); i++) {
String word = objectList.get(i).toString().toLowerCase(locale);
addWord(rootVertex, word, i);
}
}
public Vertex getRootVertex() {
return rootVertex;
}
public void addWord(Vertex vertex, String word, int index) {
if (word.isEmpty()) {
vertex.addIndexToWordsIndexList(index);
vertex.increaseWordsNumber();
}
else {
vertex.addIndexToPrefixesIndexList(index);
vertex.increasePrefixesNumber();
Character fChar = word.charAt(0);
HashMap<Character, Vertex> vertexSons = vertex.getVertexSons();
if (!vertexSons.containsKey(fChar)) {
vertex.addVertexSon(fChar);
}
word = (word.length() == 1) ? "" : word.substring(1);
addWord(vertexSons.get(fChar), word, index);
}
}
public List<Integer> getWordsIndexes(Vertex vertex, String word) {
if (word.isEmpty()) {
return vertex.getWordsIndexList();
}
else {
Character fChar = word.charAt(0);
if (!(vertex.getVertexSons().containsKey(fChar))) {
return null;
}
else {
word = (word.length() == 1) ? "" : word.substring(1);
return getWordsIndexes(vertex.getVertexSons().get(fChar), word);
}
}
}
public List<Integer> getPrefixesIndexes(Vertex vertex, String prefix) {
if (prefix.isEmpty()) {
return vertex.getWordsIndexList();
}
else {
Character fChar = prefix.charAt(0);
if (!(vertex.getVertexSons().containsKey(fChar))) {
return null;
}
else {
prefix = (prefix.length() == 1) ? "" : prefix.substring(1);
return getWordsIndexes(vertex.getVertexSons().get(fChar), prefix);
}
}
}
}
The Adapter class:
public class MunicipalitySearchAdapter extends ArrayAdapter<Municipality> {
private ArrayList<Municipality> municipalities;
private ArrayList<Municipality> allMunicipalities;
private ArrayList<Municipality> suggestedMunicipalities;
private List<Trieable> triableList;
private Trie municipalityTrie;
private int viewResourceId;
#SuppressWarnings("unchecked")
public MunicipalitySearchAdapter(Context context, int viewResourceId, ArrayList<Municipality> municipalities) {
super(context, viewResourceId, municipalities);
this.municipalities = municipalities;
this.allMunicipalities = (ArrayList<Municipality>) this.municipalities.clone();
this.suggestedMunicipalities = new ArrayList<Municipality>();
this.viewResourceId = viewResourceId;
this.triableList = new ArrayList<Trieable>();
for (Municipality mun : allMunicipalities) {
triableList.add(mun);
}
municipalityTrie = new Trie(triableList, Locale.ITALY);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(this.viewResourceId, null);
}
Municipality municipality = municipalities.get(position);
if (municipality != null) {
TextView munNameTxtView = (TextView) v.findViewById(R.id.name);
TextView proSignTxtView = (TextView) v.findViewById(R.id.sign);
TextView regNameTxtView = (TextView) v.findViewById(R.id.regionName);
if (munNameTxtView != null) {
munNameTxtView.setText(municipality.getName());
}
if (proSignTxtView != null) {
proSignTxtView.setText(municipality.getProvinceSign());
}
if (regNameTxtView != null) {
regNameTxtView.setText(municipality.getRegionName());
}
}
return v;
}
#Override
public Filter getFilter() {
return municipalityFilter;
}
Filter municipalityFilter = new Filter() {
#Override
public String convertResultToString(Object resultValue) {
String str = ((Municipality) (resultValue)).getName();
return str;
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
String constraintString = constraint.toString().trim();
suggestedMunicipalities.clear();
List<Integer> wordsIndexesList = municipalityTrie.getWordsIndexes(municipalityTrie.getRootVertex(), constraintString);
for (int index : wordsIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
List<Integer> prefixesIndexesList = municipalityTrie.getPrefixesIndexes(municipalityTrie.getRootVertex(), constraintString);
for (int index : prefixesIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
FilterResults filterRes = new FilterResults();
filterRes.values = suggestedMunicipalities;
filterRes.count = suggestedMunicipalities.size();
return filterRes;
}
else {
return new FilterResults();
}
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
#SuppressWarnings("unchecked")
ArrayList<Municipality> filteredMunicipalities = (ArrayList<Municipality>) results.values;
ArrayList<Municipality> supportMunicipalitiesList = new ArrayList<Municipality>();
clear();
for (Municipality mun : filteredMunicipalities) {
supportMunicipalitiesList.add(mun);
}
Iterator<Municipality> municipalityIterator = supportMunicipalitiesList.iterator();
while (municipalityIterator.hasNext()) {
Municipality municipality = municipalityIterator.next();
add(municipality);
}
notifyDataSetChanged();
}
}
};
}
I get a null pointer at this line (in the Filter performFltering() method implementation inside of the adapter):
for (int index : wordsIndexesList) {
suggestedMunicipalities.add(allMunicipalities.get(index));
}
Do you have any possible solutions to the problem? I can't fix it out. What am I missing or mistaking?
EDIT: I have found the problem, I needed to set the constraint toLowerCase(). Now it works. However. Why do I see the autocomplete suggestions ONLY when I type the FULL word? (in my case the name of a municipality).
It seems that my Trie doesn't return the PrefixIndexes which I can use for populating the List of suggestions. But I can't find out the problem. Any idea?
Thanks again!
To fix typing of FULL word issue mentioned by the author, getWordsIndexes should be replaced by getPrefixesIndexes, and getWordsIndexList - by getPrefixesIndexList in getPrefixesIndexes() method of Trie class:
public List<Integer> getPrefixesIndexes(Vertex vertex, String prefix)
{
if (prefix.isEmpty())
{
return vertex.getPrefixesIndexList();
}
else
{
Character fChar = prefix.charAt(0);
if (!(vertex.getVertexSons().containsKey(fChar)))
{
return null;
}
else
{
prefix = (prefix.length() == 1) ? "" : prefix.substring(1);
return getPrefixesIndexes(vertex.getVertexSons().get(fChar), prefix);
}
}
}