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);
}
}
}
Related
I'm new to OOP Programming and I'm doing a project.
At some point i've to save information into a file using json notation.
My classes:
FeedGroup
public class FeedGroup implements FeedGroupContract {
private int feedGroupID;
private String feedGroupTitle;
private String feedGroupDescription;
private Feed[] feeds;
private int tamanho;
private final int DEFAULT_SIZE = 10;
/*
private int feedGroupIDGenerator(){
}
*/
public FeedGroup(String feedGroupTitle, String feedGroupDescription) {
this.feeds= new Feed[DEFAULT_SIZE];
this.feedGroupTitle = feedGroupTitle;
this.feedGroupDescription = feedGroupDescription;
}
public FeedGroup(){
this.feeds= new Feed[DEFAULT_SIZE];
}
public FeedGroup(int feedGroupID, String feedGroupTitle, String feedGroupDescription, Feed[] feeds) {
this.feedGroupID = feedGroupID;
this.feedGroupTitle = feedGroupTitle;
this.feedGroupDescription = feedGroupDescription;
this.feeds = new Feed[DEFAULT_SIZE];
}
private void increaseSize() {
this.tamanho++;
}
#Override
public int getID() {
return this.feedGroupID;
}
#Override
public String getTitle() {
return this.feedGroupTitle;
}
#Override
public void setTitle(String string) {
this.feedGroupTitle = feedGroupTitle;
}
#Override
public String getDescription() {
return this.feedGroupDescription;
}
#Override
public void setDescription(String string) {
this.feedGroupDescription = feedGroupDescription;
}
#Override
public boolean addFeed(String feedS) throws GroupException {
Feed newFeed = new Feed();
for (int i = 0; i < this.feeds.length; i++) {
System.out.println("Saving...");
if (this.feeds[i] == null) {
//this.feeds[i] = (Feed) ;
System.out.println("Add object class: " + this.feeds[i]);
System.out.println("Saved successfully.");
increaseSize();
return true;
}
}
return false;
}
#Override
public boolean addFeed(FeedContract fc) throws GroupException {
for (int i = 0; i < this.feeds.length; i++) {
System.out.println("Saving...");
if (this.feeds[i] == null) {
this.feeds[i] = (Feed) fc;
System.out.println("Add object class: " + this.feeds[i]);
System.out.println("Saved successfully.");
increaseSize();
return true;
}
}
return false;
}
#Override
public boolean removeFeed(FeedContract fc) throws ObjectmanagementException {
boolean found = false;
for (int i = 0; i < this.feeds.length; i++) {
if (feeds[i] == fc) {
found = true;
this.feeds[i] = this.feeds[i + 1];
} else {
found = false;
}
}
return found;
}
#Override
public FeedContract getFeed(int i) throws ObjectmanagementException {
return this.feeds[i];
}
#Override
public FeedContract getFeedByID(int i) throws ObjectmanagementException {
Feed found = new Feed();
for (int j = 0; j < this.feeds.length; j++) {
if (feeds[i].getID() == i && this.feeds[i] != null) {
found = this.feeds[i];
}
}
return found;
}
#Override
public int numberFeeds() {
int count = 0;
for (Feed feed : feeds) {
if (feed != null) {
count++;
}
}
return count;
}
#Override
public void getData() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public String toString() {
return "FeedGroup{" + "feedGroupID=" + feedGroupID + ", feedGroupTitle=" + feedGroupTitle + ", feedGroupDescription=" + feedGroupDescription + ", feeds=" + feeds + ", tamanho=" + tamanho + ", DEFAULT_SIZE=" + DEFAULT_SIZE + '}';
}
}
Feed
public class Feed implements FeedContract {
private String feedTitle;
private String feedDescription;
private String feedLanguage;
private Calendar buildDate;
private FeedItem feedItemPos;
private String feedURL;
private String[] categories;
private int categoryID;
private FeedItem[] items;
private int tamanho;
private final int DEFAULT_SIZE = 10;
public Feed(String feedTitle, String feedDescription, String feedLanguage, Calendar buildDate, FeedItem feedItemPos, String feedURL, String[] categories, int categoryID) {
this.feedTitle = feedTitle;
this.feedDescription = feedDescription;
this.feedLanguage = feedLanguage;
this.buildDate = buildDate;
this.feedItemPos = feedItemPos;
this.feedURL = feedURL;
this.categories = categories;
this.categoryID = categoryID;
this.items = new FeedItem[DEFAULT_SIZE];
}
public Feed(String feedGroupURL){
}
public Feed() {
this.items = new FeedItem[DEFAULT_SIZE];
}
#Override
public String getTitle() {
return this.feedTitle;
}
#Override
public void setTitle(String string) {
this.feedTitle = feedTitle;
}
#Override
public String getDescription() {
return this.feedDescription;
}
#Override
public void setDescription(String string) {
this.feedDescription = feedDescription;
}
#Override
public String getLanguage() {
return this.feedLanguage;
}
#Override
public void setLanguage(String string) {
this.feedLanguage = feedLanguage;
}
#Override
public Calendar getBuildDate() {
return this.buildDate;
}
#Override
public void setBuildDate(Calendar clndr) {
this.buildDate = buildDate;
}
private void increaseSize() {
this.tamanho++;
}
#Override
public boolean addItem(String string, String string1, String string2, Calendar clndr, String string3, String string4) {
FeedItem item = new FeedItem(string, string1, string2, clndr, string3, string4);
System.out.println(item.getAuthor());
//System.out.println(items);
if (this.items[0] == null) {
System.out.println("ENTROU");
this.items[0] = item;
} else {
for (int i = 0; i < this.items.length; i++) {
System.out.println("AQUII");
if (items[i] == null) {
items[i] = item;
System.out.println("Add object: " + this.items[i]);
increaseSize();
return true;
}
}
}
return false;
}
#Override
public FeedItemContract getItem(int i) throws ObjectmanagementException {
return this.feedItemPos;
}
#Override
public boolean addCategory(String categoria) {
for (int i = 0; i < this.categories.length; i++) {
if (categories[i] == null) {
categories[i] = categoria;
System.out.println("Add object: " + categoria);
increaseSize();
return true;
}
}
return false;
}
#Override
public String getCategory(int i) throws ObjectmanagementException {
return this.categories[i];
}
#Override
public int numberCategories() {
int count = 0;
for (String category : categories) {
if (category != null) {
count++;
}
}
return count;
}
#Override
public int numberItems() {
int count = 0;
for (FeedItem item : items) {
if (item != null) {
count++;
}
}
return count;
}
#Override
public int getID() {
return this.categoryID;
}
#Override
public String getURL() {
return this.feedURL;
}
#Override
public void setURL(String string) throws FeedException {
this.feedURL = string;
}
}
And App
public class App implements AppContract {
private FeedGroup feedGroupPosition;
private FeedGroup feedGroupID;
private Tag tag;
private FeedItem feedItem;
private FeedGroup[] groups;
private int tamanho;
public App() {
this.groups = new FeedGroup[10];
}
private void increaseSize() {
this.tamanho++;
}
/**
* Método para adicionar um grupo
*
* #param string titulo do grupo
* #param string1 descrição do grupo
* #return true se adicionar, false se não o fizer
*/
#Override
public boolean addGroup(String string, String string1) {
FeedGroup group = new FeedGroup(string, string1);
//System.out.println(group);
//System.out.println(this.groups.length);
if (this.groups.length == 0) {
this.groups[0] = group;
} else {
for (int i = 0; i < this.groups.length; i++) {
System.out.println("Saving...");
if (this.groups[i] == null) {
this.groups[i] = group;
System.out.println("Add object class: " + this.groups[i]);
System.out.println("Saved successfully.");
increaseSize();
//System.out.println(this.groups.length);
return true;
}
}
}
return false;
}
#Override
public boolean removeGroup(int i) throws ObjectmanagementException {
boolean found = false;
for (int j = i; j < this.groups.length; j++) {
if (groups[j] != null) {
found = true;
this.groups[j] = this.groups[j + 1];
} else {
found = false;
}
}
return found;
}
#Override
public FeedGroupContract getGroup(int i) throws ObjectmanagementException {
return this.groups[i];
}
#Override
public FeedGroupContract getGroupByID(int i) throws ObjectmanagementException {
FeedGroup found = new FeedGroup();
for (int j = 0; j < this.groups.length; j++) {
if (groups[i].getID() == i && this.groups[i] != null) {
found = this.groups[i];
}
}
return found;
}
#Override
public int numberGroups() {
int count = 0;
for (FeedGroup group : groups) {
if (group != null) {
count++;
}
}
return count;
}
#Override
public FeedItemContract[] getItemsByTag(String string) {
//for(int i = 0; i<)
return null;
}
#Override
public void saveGroups() throws Exception {
JSONArray jsonArray = new JSONArray();
JSONObject jsonObject = new JSONObject();
for (int i = 0; i < this.groups.length; i++) {
FeedGroup fg = (FeedGroup) this.getGroup(i);
JSONArray jsonArrayTemp = new JSONArray();
for (int j = 0; j < fg.numberFeeds(); j++) {
Feed feed = (Feed) fg.getFeed(i);
jsonArrayTemp.add(feed.getURL());
}
jsonObject.put("Group", jsonArrayTemp);
jsonObject.put("Title", groups[i].getTitle());
jsonObject.put("Description", groups[i].getDescription());
// System.out.println("URL: "+ groups[i].getFeed(i).getURL());
jsonObject.put("URL", groups[i].getFeed(i).getURL());
// if(groups[i].getFeed(i).getURL() != null){
// jsonObject.put("URL", groups[i].getFeed(i).getURL());
// } else {
// jsonObject.put("URL", "");
// }
jsonArray.add(jsonObject);
}
FileWriter file = null;
file = new FileWriter("group.json");
file.write(jsonArray.toJSONString());
file.flush();
}
#Override
public void loadGroups() throws Exception {
}
#Override
public FeedGroupContract[] getAllGroups() {
return this.groups;
}
#Override
public FeedItemContract[] getAllSavedItems() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public boolean removeSavedItem(int i) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
The App class is a kind of Container of Objects.
So in resumen, i want to save FeedGroup attributes in a file using json, and then load the file. These methods are implemented in App class (saveGroup() and loadGroup()).
FeedGroup has an instance of Feed class: "private Feed[] feeds" and from Feed class, i just want to get the feedURL to save in the file.
Am i doing it right?
I've tried to do loadResults() method seeing other projects(yes, even without knowing if the saveGroup() method was done correctly) and i got the idea. I've to use set and then valueOf, but i think that without the saveGroup() method done correctly, worth nothing to me.
Can someone help me?
Sorry for the long(?) description.
Thanks.
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!
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 show a selectable list with all launchable installed apps. I wan't to save the selections, but the ListPreference saved all listed entries.
Where is my mistake?
Here is my ListPreference:
public class SettingsSelectsApps extends ListPreference {
private String separator;
private static final String DEFAULT_SEPARATOR = "\u0001\u0007\u001D\u0007\u0001";
private boolean[] entryChecked;
public SettingsSelectsApps(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
loadEntries();
entryChecked = new boolean[getEntries().length];
separator = DEFAULT_SEPARATOR;
}
public SettingsSelectsApps(Context context) {
this(context, null);
}
private void loadEntries() {
final Context context = getContext();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<ResolveInfo> pkgAppsList = context.getPackageManager().queryIntentActivities( mainIntent, 0);
CharSequence[] entries = new CharSequence[pkgAppsList.size()];
CharSequence[] entryValues = new CharSequence[pkgAppsList.size()];
int j = 0;
for ( ResolveInfo P : pkgAppsList ) {
entryValues[j] = (CharSequence) P.getClass().getName();
entries[j] = P.loadLabel(context.getPackageManager());
++j;
};
setEntries(entries);
setEntryValues(entryValues);
}
#Override
protected void onPrepareDialogBuilder(Builder builder) {
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null || entries.length != entryValues.length) {
throw new IllegalStateException(
"MultiSelectListPreference requires an entries array and an entryValues "
+ "array which are both the same length");
}
restoreCheckedEntries();
OnMultiChoiceClickListener listener = new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean val) {
entryChecked[which] = val;
}
};
builder.setMultiChoiceItems(entries, entryChecked, listener);
}
private CharSequence[] unpack(CharSequence val) {
if (val == null || "".equals(val)) {
return new CharSequence[0];
} else {
return ((String) val).split(separator);
}
}
public CharSequence[] getCheckedValues() {
return unpack(getValue());
}
private void restoreCheckedEntries() {
CharSequence[] entryValues = getEntryValues();
CharSequence[] vals = unpack(getValue());
if (vals != null) {
List<CharSequence> valuesList = Arrays.asList(vals);
for (int i = 0; i < entryValues.length; i++) {
CharSequence entry = entryValues[i];
entryChecked[i] = valuesList.contains(entry);
}
}
SharedPreferences prefs = getSharedPreferences();
try{
Map<String,?> keys = prefs.getAll();
for(Map.Entry<String,?> entry : keys.entrySet()){
}
} catch(NullPointerException e) {
Log.d("map values","Error: "+e);
}
}
#Override
protected void onDialogClosed(boolean positiveResult) {
List<CharSequence> values = new ArrayList<CharSequence>();
CharSequence[] entryValues = getEntryValues();
if (positiveResult && entryValues != null) {
for (int i = 0; i < entryValues.length; i++) {
if (entryChecked[i] == true) {
String val = (String) entryValues[i];
values.add(val);
}
}
String value = join(values, separator);
setSummary(prepareSummary(values));
setValueAndEvent(value);
}
}
private void setValueAndEvent(String value) {
if (callChangeListener(unpack(value))) {
setValue(value);
}
}
private CharSequence prepareSummary(List<CharSequence> joined) {
List<String> titles = new ArrayList<String>();
CharSequence[] entryTitle = getEntries();
CharSequence[] entryValues = getEntryValues();
int ix = 0;
for (CharSequence value : entryValues) {
if (joined.contains(value)) {
titles.add((String) entryTitle[ix]);
}
ix += 1;
}
return join(titles, ", ");
}
#Override
protected Object onGetDefaultValue(TypedArray typedArray, int index) {
return typedArray.getTextArray(index);
}
#Override
protected void onSetInitialValue(boolean restoreValue,
Object rawDefaultValue) {
String value = null;
CharSequence[] defaultValue;
if (rawDefaultValue == null) {
defaultValue = new CharSequence[0];
} else {
defaultValue = (CharSequence[]) rawDefaultValue;
}
List<CharSequence> joined = Arrays.asList(defaultValue);
String joinedDefaultValue = join(joined, separator);
if (restoreValue) {
value = getPersistedString(joinedDefaultValue);
} else {
value = joinedDefaultValue;
}
setSummary(prepareSummary(Arrays.asList(unpack(value))));
setValueAndEvent(value);
}
protected static String join(Iterable<?> iterable, String separator) {
Iterator<?> oIter;
if (iterable == null || (!(oIter = iterable.iterator()).hasNext()))
return "";
StringBuilder oBuilder = new StringBuilder(String.valueOf(oIter.next()));
while (oIter.hasNext())
oBuilder.append(separator).append(oIter.next());
return oBuilder.toString();
}
}
I believe you use code from this gist which works great except for the separator. I don't know why did the author choose the separator to be "\u0001\u0007\u001D\u0007\u0001".
Once I tried to change the separator to any other string, for example ",," everything worked.
The separator string chosen seems to cause android to be unable to parse the shared preferences xml document.
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.