I have a recordset of about 29,000 records. My Screen contains EditText Box for Search Criteria and Listview containing all 29,000 records.
By searching with the listed way it takes time and not giving flow less output as I need.
My EditText contains
final EditText txtSearchCity = (EditText) findViewById(R.id.edtCity);
txtSearchCity.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
aCountryIDTemp.clear();
aCityStateTemp.clear();
for (int i = 0; i < aCountryID.size(); i++) {
if (aCityState
.get(i)
.toLowerCase()
.contains(
txtSearchCity.getText().toString()
.toLowerCase())) {
aCountryIDTemp.add(aCountryID.get(i));
aCityStateTemp.add(aCityState.get(i));
}
}
BindList();
}
});
}
BindList() method is setting the arraylist aCityStateTemp to adapter.
Any Other way to Search and Create new ArrayList dynamically.
I would insist to use Lambdaj Library which is mostly used in such cases where you want to restrict loops for sorting and filtering Collections.
Here is a small example for using lambdaj for filtering ArrayList.
ArrayList<String> sortedArrayList = select(arrList, having(on(String.class),
Matchers.containsString("a");
This will return a complete filtered ArrayList with which you want to populate your ListView.
You can also filter Custom Classes - Java: What is the best way to filter a Collection?
UPDATE:
Above solution was case-sensitive so to work around you can add Multiple Matchers.
Like this you can add Multiple Matchers,
ArrayList<String> sortedArrayList = select(arrList, having(on(String.class),
(Matchers.anyOf(Matchers.containsString("a"),Matchers.containsString("A")))));
UPDATE:
Even better way is to use filter(Matcher<?> matcher, T...array)
Here is how you can do that,
ArrayList<String> sortedArrayList = filter(Matchers.anyOf(
Matchers.containsString("a"),Matchers.containsString("A")), arrList);
Also, if you are interested in using some of the methods/features of lambdaj, you can extract the source and get it working. I am adding the same for filter()
You can just download hamcrest-all-1.0.jar(63 kb) and add below code to get the filter() working
public static <T> List<T> filter(Matcher<?> matcher, Iterable<T> iterable) {
if (iterable == null)
return new LinkedList<T>();
else{
List<T> collected = new LinkedList<T>();
Iterator<T> iterator = iterable.iterator();
if (iterator == null)
return collected;
while (iterator.hasNext()) {
T item = iterator.next();
if (matcher.matches(item))
collected.add(item);
}
return collected;
}
}
So, you can just sort out the least from lambdaj source and integrate in your source.
You can use HashSet or LinkedHashSet(keeps insertion order) for fast searching.
With contains() method of that classes.
I would assume that you have passed aCityStateTemp to you Adapter as ArrayList while initializing the Adapter
Now after changing the contents of aCityStateTemp, you just need to call adapter.notifyDataSetChanged(). you don't need to set the aCityStateTemp to adapter as new ArrayList.
You can store all data in a sqlite database and retrieve the searched item using like query.
Related
I wanted to write a query so as to filter the data using an edittext, the code below does work but bring all of the unwanted searched data. Can you please help me? The JSON object is as follows: The data I wanted to filter out is the username
public class SearchActivity extends AppCompatActivity {
ListView searchList;
DatabaseReference databaseReference;
FirebaseListAdapter<SearchDetails> listAdapter;
String search;
EditText editTextSearch;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
searchList = (ListView) findViewById(R.id.listViewSearch);
databaseReference = FirebaseDatabase.getInstance().getReference().child("Search Users");
editTextSearch = (EditText) findViewById(R.id.editTextSearch);
editTextSearch.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
search = editTextSearch.getText().toString();
if (search.equals("")){
searchList.setAdapter(null);
}else{
searchList.setAdapter(listAdapter);
}
}
});
Query query = databaseReference.startAt(search).endAt(search+"~").limitToFirst(10);
listAdapter = new FirebaseListAdapter<SearchDetails>(
this,
SearchDetails.class,
R.layout.search_layout,
query
) {
#Override
protected void populateView(View v, SearchDetails model, int position) {
TextView username = (TextView) v.findViewById(R.id.textViewUsername);
username.setText(model.getUsername());
TextView name = (TextView) v.findViewById(R.id.textViewName);
name.setText(model.getName());
}
};
}
}
In order to solve this problem we need to understand a few things about Firebase, that a lot of developers might overlook.
It is a NoSQL based Data Storage, so you have to structure your data accordingly.
Firebase allows you to nest nodes into your data structure for up to 32 levels deep, and this can be easily abused.
The problem that I think you have at hand is to "have an EditText that allows user to search for usernames of your users.
How I would suggest you to tackle this is by flattening the structure of your data like so
You can have objects inside usernames array. Each object's key is the username and the value is another object with an empty-key : "" like so.
And you can then have another array of users where again the keys are usernames and rest of the data lives as an object under it.
Now you can easily run your Filter Query on usernames without worrying about the overhead of fetching unnecessary data in that query.
And once you know which username was selected, you can query users to fetch that exact user from the array.
Query query = databaseReference.orderByChild("username").startAt(search).endAt(search+"~").limitToFirst(10);
public void setSearch(ArrayList<Search> ListSearch){
search=ListSearch;
removeInActiveClasses(search);
notifyItemRangeChanged(0,search.size());
}
public void removeInActiveClasses(ArrayList<Search> data){
for(int i=0;i<data.size();i++){
boolean isActive=Boolean.parseBoolean(data.get(i).getActive());
System.out.println("The course at Not Removed "+search.get(i).getName()+" is set to "+search.get(i).getActive());
if(!isActive){
System.out.println("The course at Removed"+search.get(i).getName()+" is set to "+search.get(i).getActive());
search.remove(i);
}
}
}
A list is passed through as listSearch and it contains a list of courses, if the courses are set to active which is a string that either true or false, and parsed as a boolean, then the item should be removed. I am certain I did the parsing correctly so I am wondering what is going on here? How come it does not delete all the false courses?
You might wanna create another instance of ArrayList and set your search to that one because your are accessing and modifying your ArrayList at simultaneously.
Other notes:
Please use camelCase for your argument names. So instead of ListSearch, use searchList.
For your class variable, try adding m in front so you won't get confused. So instead of search, use mSearchList
Lastly, you are mixing some variables within one method. Try unifying them for better maintenance.
Here's the full code.
public void setSearchList(ArrayList<Search> searchList) {
mSearchList = removeInactiveClasses(searchList);
notifyDataSetChanged();
}
private ArrayList<Search> removeInactiveClasses(ArrayList<Search> data) {
ArrayList<Search> list = new ArrayList<>();
for (int i = 0; i < data.size(); i++){
boolean isActive = Boolean.parseBoolean(data.get(i).getActive());
if (isActive){
list.add(data.get(i));
}
}
return list;
}
I'm writing custom BaseAdapter (backed by Set), and I need to filter the data by something other then CharSequence (price range, color, etc.).
I looked at Filterable interface and Filter class, but all the methods from Filter uses CharSequences.
So, how to do that? Should I encode the filter parameters to CharSequence (as JSON, maybe?), or is there some better way?
Also, what's the convertResultToString method good for? Because I don't need the result to be String, but Object from Set, because I'm displaying more information then just a String.
If you implements Filterable and Filter and, since your adapter is backed by a set of elements, you just can ignore the CharSequences in the public FilterResults performFiltering(CharSequence prefix) method. Inside your adapter, make a copy of your set of elements, which will be the one you'll use to fill the ListView or whatever you were using.
After that, inside the performFiltering(...) method just remove the elements by your choosen criteria:
private class ClientsFilter extends Filter {
#Override
public FilterResults performFiltering(CharSequence prefix) {
// Thats the result of our filtering process
FilterResults results = new FilterResults();
synchronized (mLock) {
// It's a good idea to work with a copy of the original set of elements, so we can reuse it every time we want to filter
ArrayList<Client> filteredClients = new ArrayList<>(originalClients);
for (int i = 0; i < filteredClient.size(); i++) {
if (// !fit my criteria) {
// We remove the clients that doesn`t fit the criteria, so only the good ones stay here
filteredClients.remove(i);
i--;
}
}
results.values = filteredClients;
results.count = filteredClients.size();
}
return results;
}
#Override
protected void publishResults(CharSequence prefix, FilterResults results) {
// And we use the filtered clients in our ListView, GridView or whatever
listViewClients= (ArrayList<Client>) results.values;
adapter.notifyDataSetChanged();
}
}
Some tips:
Don't forget to use the synchronized stuff, or you'll have some funny errors (NPE without any visible reason caused by race conditions)
Using Filterable the filtering process is doing in background, so UI keeps responsive
Call the adapter.getFilter().filter() method when you want to filter your results, like for example when writing in an EditText
You can filter as you want but it's required to write some code.
In your adapter, create another field for your set. It will be the set you actually display. That mean that in all your getCount, getView, getItemId, getItem methods you should use this second set (in fact evrywhere in your adapter). By default, it should be the same as you primary set of data. Your primary set of data will be mSet and the display mDisplaySet.
Create a custom filter method. This method, will filter put in your second set (the one you actually display) the element of your first set that match the given condition :
public void filter(Object condition) {
mDisplaySet = new Set<>();
for (Object object : mSet) {
if (object.match(condition) {
mDisplaySet.add(object);
}
}
notifyDataSetChanged();
}
I looked detail at your questions and I will answer 1 by 1.
1. So, how to do that? Should I encode the filter parameters to CharSequence (as JSON, maybe?), or is there some better way?
I think you should not encode it, just do it as below:
public class YourFilterObject {
String test1;
int test2;
}
private YourFilterObject yourFilterObject;
public TestAdapter setFilterObject(YourFilterObject object) {
this.yourFilterObject = object;
return this;
}
#Override
public Filter getFilter() {
return new Filter() {
#Override
protected FilterResults performFiltering(CharSequence charSequence) {
// use yourFilterObject here to filter your adapter
return null;
}
#Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
}
};
}
Whenever you want to filter your adapter with custom parameters (not just string), you can call call YourAdapter.setFilterObject(object).getFilter().filter(null);
2. what's the convertResultToString method good for
Actually this method I use only for AutocompleteTextView. When you type something (example: name...) in AutocompleteTextView, it will show you a list of object in filterResults to choose. When you pick up a result object, the convertResultToString function will be called to get the Text to be set (show) in AutocompleteTextView. If you dont override this method, after picking an object, the AutocompleteTextView will show nothing.
Hope this help.
I have a application already working fine. I need to implement a quick search feature to it. Quick search i mean as the users types in every single character i want to result to be fetched. My Activity is a list activity for which the data is coming from the database query. Say the list view has 50 items, and when the user searches with word as "test" I want to query the database and filter the items and display it in the same list view. Something like the contact search in android. Please let me know how to implement this. A quick sample would be help full. Thank you.
you can do by this
I implemented this feature in one of my previous app. The entire code is too long to be pasted here. So, I have posted some portions of it. You may read a bit about the approach used by me and get it done for your app.
EditText filterText;
words = new MySimpleCursorAdapter(Main.this, R.layout.emptylist, temp, from, to);
filterText= (EditText) findViewById(R.id.search_box);
filterText.addTextChangedListener(filterTextWatcher);
words.setFilterQueryProvider(new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
Cursor cur=mDbHelper.fetchSugNotes(filterText.getText().toString());
return cur;
}
});
private TextWatcher filterTextWatcher = new TextWatcher() {
public void afterTextChanged(android.text.Editable s) {
};
public void beforeTextChanged(CharSequence s, int start, int count, int after) {};
public void onTextChanged(CharSequence s, int start, int before, int count) {
words.getFilter().filter(s.toString());
};
};
I have an Activity with an AutoComplete box on it. Whenever the text changes, I want to call a web service and populate the ArrayAdapter with the new String[] returned. This part all works great, except the list in the UI isn't being refreshed when there are all new values in String[] schools. The original list populated in my onCreate always remains.
I read somewhere I needed to update it on the same thread the UI is running on, so I tried the Runnable listed in my code below. But that, as well as just updating my class variable schools does not work alongside notifyOnDataSetChange()
Where am I going wrong?
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
schools = SchoolProxy.search("Co");
autoCompleteAdapter = new ArrayAdapter(this,android.R.layout.simple_dropdown_item_1line, schools);
autoComplete = (AutoCompleteTextView) findViewById(R.id.edit);
autoComplete.addTextChangedListener(textChecker);
autoComplete.setAdapter(autoCompleteAdapter);
}
...
....
...
final TextWatcher textChecker = 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)
{
schools = SchoolProxy.search(s.toString());
runOnUiThread(updateAdapter);
}
};
private Runnable updateAdapter = new Runnable() {
public void run() {
autoCompleteAdapter.notifyDataSetChanged();
}
};
As a less important side note, if each item in schools has a name \n city,state. Is it possible to have the city & state on a second line within the auto drop down box? Just for formatting purposes. Would look cleaner if I could.
Thank you!
Have you tried the setNotifyOnChange(true)? It is supposed to properly do what you are doing manually whenever you use methods that change the list (add(T), insert(T, int), remove(T), clear()). Perhaps you have to modify the array through these methods on the ArrayAdapter?
I'm not sure if the ArrayAdapter is actually holding the reference to schools or just copied the contents while constructing the ArrayAdapter. Maybe you can take a look at the AOSP code to see what they're doing in that constructor.
After you dynamically change adapter, just need to call:
AutoCompleteTextView.showDropdown()