I'm trying to implement search filter with getFilter method in my custom adapter.
There are two models of search through the list.
search for title of an item,
search for genre[] of an item.
(But I'm just talking about genre here).
The below code is working fine, but what I need is just a little more customized way to search in genre[].
What I'm trying to achieve is: (What I want):
multi-keyword searching, each category separated with,. example: genre1,genre2
(What I have done):
When I type genre1,genre2 then it gives the items that have this sequence in genres, it won't give genre1,genre3,genre2 , or just genre1.
Can you help, please?
getFilter:
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
dataList = (List<ProductLocal>) results.values;
notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<ProductLocal> filteredList = new ArrayList<>();
constraint = constraint.toString().toLowerCase();
for (int i = 0; i < dataListFilter.size(); i++) {
ProductLocal dataNames = dataListFilter.get(i);
String genreStr = "";
for (String str : dataNames.getGenre()) {
genreStr += str + ",";
}
if (dataNames.getTitle().toLowerCase().startsWith(constraint.toString())
|| genreStr.toLowerCase().contains(constraint.toString())) {
filteredList.add(dataNames);
}
}
results.count = filteredList.size();
results.values = filteredList;
return results;
}
};
return filter;
}
AFAIU you want a song to match the fiter if any of these 2 conditions are met:
Song's title starts with whole search text
The song has a genre that contains a part of the search text separated by , (comma)
If this is so, try following search logic:
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
FilterResults results = new FilterResults();
List<ProductLocal> filteredList = new ArrayList<>();
String searchText = constraint.toString().toLowerCase();
String[] split = searchText.split(",");
ArrayList<String> searchGenres = new ArrayList<String>(split.length);
for (int i = 0; i < split.length; i++)
{
// remove spaces
String trim = split[i].trim();
// skip empty entries
if (trim.length() > 0)
searchGenres.add(trim);
}
for (ProductLocal dataNames : dataListFilter)
{
// filter by title
if (dataNames.getTitle().toLowerCase().startsWith(searchText))
{
filteredList.add(dataNames);
}
else
{
// filter by genres
// search for at least one common genre between song and search text
outer:
for (String songGenre : dataNames.getGenre())
{
for (String searchGenre : searchGenres)
{
if (songGenre.toLowerCase().contains(searchGenre))
{
filteredList.add(dataNames);
break outer;
}
}
}
}
}
results.count = filteredList.size();
results.values = filteredList;
return results;
}
The only tricky place here is break outer;. This is a Java syntax to break out from two for loops at the same time. The idea here is that if we've found a match by some genre between constraint and a song, we add the song to the filteredList and don't want to add it again if there are more matching genres.
P.S. if filtering by many search genres becomes a performance issue, you may consider building single RegEx to match all search terms at once or implementing Aho–Corasick algorithm
Please try by splitting constraint string like below:
#Override
public Filter getFilter() {
#SuppressWarnings("UnnecessaryLocalVariable")
Filter filter = new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
dataList = (List<ProductLocal>) results.values;
notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<ProductLocal> filteredList = new ArrayList<>();
for (int i = 0; i < dataListFilter.size(); i++) {
ProductLocal dataNames = dataListFilter.get(i);
int count = 0;
String[] terms = constraint.toString().split(",");
for (int j = 0; j < terms.length; j++) {
String term = terms[j];
for (String str : dataNames.getGenre()) {
if (str.equals(term) || str.contains(term)) {
count++;
if (count == terms.length) {
filteredList.add(dataNames);
}
}
}
}
}
results.count = filteredList.size();
results.values = filteredList;
return results;
}
};
return filter;
}
You can split search term by ,
String[] searmTearmArray= serachTerm.split(",");
now using for loop you can search these terms.
Related
I have an ArrayList where I only want to show the Items that contain the strings "dish" or "sideDish". At first I had a for-loop in the getItem function but it wasn't good for the performance so I tried to do it with a filter.
public Filter getFilter(){
return mFilter;
}
private class ItemFilter extends Filter{
#Override
protected FilterResults performFiltering(CharSequence constraint) {
String filterString= constraint.toString().toLowerCase();
FilterResults results = new FilterResults();
final List<CanteenMenu> list = listData;
int count = list.size();
final List<CanteenMenu> nlist = new ArrayList<CanteenMenu>(count);
String filterableString;
for(int i = 0; i < count; i++){
filterableString = list.get(i);
if(filterableString.toLowerCase().contains("dish" || "sideDish")){
nlist.add(filterableString);
}
}
results.values = nlist;
results.count = nlist.size();
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
filteredData = (ArrayList<CanteenMenu>) results.values;
}
}
The problems are:
- the or || doens't work
- the filterableString = list.get(i); gives the error icompatible types
- nlist.add(filterableString); gives the error list cannot be applied to string
Can anyone help?
I have AutoCompletTextView and i want do apply some customize filetring to it for this i have this code
public class ATCAdapter extends ArrayAdapter<String> implements Filterable {
ArrayList<String> _items = new ArrayList<String>();
ArrayList<String> orig = new ArrayList<String>();
public ATCAdapter(Context context, int resource, ArrayList<String> items) {
super(context, resource, items);
for (int i = 0; i < items.size(); i++) {
orig.add(items.get(i));
}
}
#Override
public int getCount() {
if (_items != null)
return _items.size();
else
return 0;
}
#Override
public String getItem(int arg0) {
return _items.get(arg0);
}
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
if(constraint != null) {
Log.d("Constraints", constraint.toString());
}
FilterResults oReturn = new FilterResults();
/* if (orig == null){
for (int i = 0; i < items.size(); i++) {
orig.add(items.get(i));
}
}*/
String temp;
int counters = 0;
if (constraint != null){
_items.clear();
if (orig != null && orig.size() > 0) {
for(int i=0; i<orig.size(); i++)
{
temp = orig.get(i).toUpperCase();
if(temp.startsWith(constraint.toString().toUpperCase()))
{
_items.add(orig.get(i));
counters++;
}
}
}
Log.d("REsult size:" , String.valueOf(_items.size()));
if(counters != 0)
{
_items.clear();
_items = orig;
}
oReturn.values = _items;
oReturn.count = _items.size();
}
return oReturn;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if(results != null && results.count > 0) {
notifyDataSetChanged();
}
else {
notifyDataSetInvalidated();
}
}
};
return filter;
}
}
and this is how i am setting adapter for
AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView);
ArrayAdapter adapter = new ATCAdapter(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(languages)));
autoCompleteTextView.setThreshold(1);
autoCompleteTextView.setAdapter(adapter);
Now the problem is i always get null parameter in performFiletring(), Any reason why it's happening ?
IHMO, your code has a problem at
if(counters != 0)
{
_items.clear();
_items = orig;
}
I suggest you update your code as the following:
#Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
Log.d("Constraints", constraint.toString());
}
FilterResults oReturn = new FilterResults();
String temp;
int counters = 0;
if (constraint != null && constraint.length() > 0) {
_items.clear();
if (orig != null && orig.size() > 0) {
for (int i = 0; i < orig.size(); i++) {
temp = orig.get(i).toUpperCase();
if (temp.startsWith(constraint.toString().toUpperCase())) {
_items.add(orig.get(i));
counters++;
}
}
}
Log.d("Result size:", String.valueOf(_items.size()));
if (counters == 0) {
_items = new ArrayList<>(orig);
}
oReturn.values = _items;
oReturn.count = _items.size();
} else {
_items = new ArrayList<>(orig);
oReturn.values = _items;
oReturn.count = _items.size();
}
return oReturn;
}
and the constructor:
public ATCAdapter(Context context, int resource, ArrayList<String> items) {
super(context, resource, items);
for (int i = 0; i < items.size(); i++) {
orig.add(items.get(i));
}
_items = new ArrayList<>(orig); // ADD THIS LINE
}
In Arraylist, how can one filtered the arraylist to rows between 2 dates.
For example,
find all the students in arraylist from october to december 2015.
new student ("12/05/2015",john, level2);
new student ("13/05/2015",bisnet, level2);
new student ("5/05/2015",tube, level2);
new student ("7/05/2015",eros, level2);
new student ("4/05/2015",mackay, level2);
new student ("8/05/2015",walnet, level2);
Here is my working. if the date is between the start and ending date. It must display true else its false
public boolean systemDate(Date startDate, Date endDate, String exps) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date expenseLsDate=null;
try {
expenseLsDate = sdf.parse(exps.substring(0,10));
} catch (ParseException e) {}
// Date expenseLsDate = dateFortmat(exps);
//SimpleDateFormat dateFormat = new SimpleDateFormat("dd-mmm-yyyy");//dd-mmm-yyyy hh:mm:ss aa
// Date convertedDate = new Date();
// ArrayList<Expenses> filteredDate = new ArrayList<>();
// Iterator<Expenses> iterator = lList.iterator();
/* for (Expenses temp : lList) {
try {
convertedDate = dateFormat.parse(temp.date_of_entry);
} catch (Exception e) {
e.printStackTrace();
}*/
if ((expenseLsDate.before(endDate)) && (expenseLsDate.after(startDate))) //here "date2" and "date1" must be converted to dateFormat
{
Log.e("valid","true");
//filteredDate.add(convertedDate); // You can use these filtered ArrayList
return true;
}
//}
Log.e("valid","false");
return false;
}
The above method is used to filter my baseadapter.
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,FilterResults results) {
mDisplayedValues = (ArrayList<Expenses>) results.values; // has the filtered values
Log.e("c", String.valueOf(mDisplayedValues.size()));
if (mDisplayedValues.isEmpty()||mDisplayedValues==null){
Log.e("FIELD_EMPTY", "NO_RECORD_FOUND");
}
notifyDataSetChanged(); // notifies the data with new filtered values
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults(); // Holds the results of a filtering operation in values
ArrayList<Expenses> FilteredArrList = new ArrayList<Expenses>();
if (mOriginalValues == null) {
mOriginalValues = new ArrayList<Expenses>(mDisplayedValues); // saves the original data in mOriginalValues
}
/******
*
* If constraint(CharSequence that is received) is null returns the mOriginalValues(Original) values
* else does the Filtering and returns FilteredArrList(Filtered)
*
*****/
if (constraint == null || constraint.length() == 0) {
// set the Original result to return
results.count = mOriginalValues.size();
results.values = mOriginalValues;
} else {
/* public Expenses(String expenses_id, String date_of_entry, String name, String category_id,
String amount, String currency_id, String comments,
String fileid, String user_id) */
constraint = constraint.toString().toLowerCase();
for (int i = 0; i < mOriginalValues.size(); i++) {
String expName = mOriginalValues.get(i).name;
String expAmount = mOriginalValues.get(i).amount;
String expComment = mOriginalValues.get(i).comments;
// Date startDate = systemUtils.dateFortmat(mOriginalValues.get(i).date_of_entry);
if (date1!=null && date2!=null)
isDateValid = systemUtils.systemDate(date1,date2,mOriginalValues.get(i).date_of_entry);
if (expName.toLowerCase().contains(constraint.toString()) || (expAmount.contains(constraint.toString())
|| (expComment.contains(constraint.toString())&& isDateValid))) {
FilteredArrList.add(
new Expenses(mOriginalValues.get(i).expenses_id,
mOriginalValues.get(i).date_of_entry,
mOriginalValues.get(i).name,
mOriginalValues.get(i).category_id,
mOriginalValues.get(i).amount,
mOriginalValues.get(i).currency_id,
mOriginalValues.get(i).comments,
mOriginalValues.get(i).fileid,
mOriginalValues.get(i).user_id ));
}
}
// set the Filtered result to return
results.count = FilteredArrList.size();
results.values = FilteredArrList;
}
return results;
}
};
return filter;
}
I have a ListView of over 1000 items, this list is filterable by a Search function in my Adapter, when clicking on an item it replace the current fragment (The one with the list(A)) with a detail fragment (B). Upon the user pressing back or returning to the previous fragment (B) there are duplicate list items.
Any ideas??
public class HallsInStateAdapter extends BaseAdapter implements Filterable {
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private ArrayList<HashMap<String, String>> orginalData;
private static LayoutInflater inflater=null;
private Filter hallFilter;
public HallsInStateAdapter(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
setData(d);
this.orginalData = d;
}
public int getCount() {
return getData().size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi = inflater.inflate(R.layout.hall_list_view, null);
TextView hallName = (TextView)vi.findViewById(R.id.hallName);
TextView hallSuburb = (TextView)vi.findViewById(R.id.hallAddress);
ImageView hallFavIcon = (ImageView)vi.findViewById(R.id.hallFavouriteIcon);
HashMap<String, String> listData = new HashMap<String, String>();
listData = getData().get(position);
String address = listData.get(HallsInStateFragment.KEY_HALL_ADDRESS);
String suburb = listData.get(HallsInStateFragment.KEY_SUBURB);
String state = listData.get(HallsInStateFragment.KEY_STATE);
String objectID = listData.get(HallsInStateFragment.KEY_OBJECTID);
hallName.setText(address);
hallSuburb.setText(suburb + ", " + state);
hallFavIcon.setVisibility(View.INVISIBLE);
boolean isFavourite = false;
DatabaseHandler db = new DatabaseHandler(activity);
List<Favourite> favs = db.getAllFavourites();
for (int i = 0; i < favs.size(); i++){
if(favs.get(i).getObjectId().equals(objectID)){
isFavourite = true;
break;
}
else {
isFavourite = false;
}
}
db.close();
if (isFavourite == true){
hallFavIcon.setVisibility(View.VISIBLE);
}
return vi;
}
public android.widget.Filter getFilter() {
if (hallFilter == null)
hallFilter = new HallFilter();
return hallFilter;
}
public ArrayList<HashMap<String, String>> getData() {
return data;
}
public void setData(ArrayList<HashMap<String, String>> data) {
this.data = data;
}
private class HallFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We implement here the filter logic
if (constraint == null || constraint.length() == 0) {
// No filter implemented we return all the list
results.values = orginalData;
results.count = orginalData.size();
}
else {
// We perform filtering operation
final ArrayList<HashMap<String, String>> filteredLocations = new ArrayList<HashMap<String, String>>();
for (int i = 0; i < orginalData.size(); i ++) {
HashMap<String, String> halls = new HashMap<String, String>();
halls.put(HallsInStateFragment.KEY_OBJECTID, orginalData.get(i).get(HallsInStateFragment.KEY_OBJECTID));
String name = orginalData.get(i).get(HallsInStateFragment.KEY_NAME);
String prefix = orginalData.get(i).get(HallsInStateFragment.KEY_PREFIX);
String address = null;
if(prefix == null || prefix.length() == 0){
address = name;
}
else {
address = prefix + " " + name;
}
halls.put(HallsInStateFragment.KEY_NAME, name);
halls.put(HallsInStateFragment.KEY_PREFIX, prefix);
halls.put(HallsInStateFragment.KEY_HALL_ADDRESS, address);
halls.put(HallsInStateFragment.KEY_STREET, orginalData.get(i).get(HallsInStateFragment.KEY_STREET));
halls.put(HallsInStateFragment.KEY_SUBURB, orginalData.get(i).get(HallsInStateFragment.KEY_SUBURB));
halls.put(HallsInStateFragment.KEY_STATE, orginalData.get(i).get(HallsInStateFragment.KEY_STATE));
halls.put(HallsInStateFragment.KEY_POSTCODE, orginalData.get(i).get(HallsInStateFragment.KEY_POSTCODE));
halls.put(HallsInStateFragment.KEY_LATITUDE, orginalData.get(i).get(HallsInStateFragment.KEY_LATITUDE));
halls.put(HallsInStateFragment.KEY_LONGITUDE, orginalData.get(i).get(HallsInStateFragment.KEY_LONGITUDE));
halls.put(HallsInStateFragment.KEY_TYPE, orginalData.get(i).get(HallsInStateFragment.KEY_TYPE));
String query = constraint.toString().toLowerCase();
String suburb = orginalData.get(i).get(HallsInStateFragment.KEY_SUBURB);
if(name == null || name.length() == 0){
Log.e("SGL", "NULL");
}
else {
if (name.toLowerCase().contains(query) || suburb.toLowerCase().contains(query)) {
Log.i("SGL-QUERY", query);
filteredLocations.add(halls);
}
}
results.values = filteredLocations;
results.count = filteredLocations.size();
}
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//Now we have to inform the adapter about the new list filtered
if (results.count == 0)
notifyDataSetInvalidated();
else {
setData((ArrayList<HashMap<String, String>>) results.values);
notifyDataSetChanged();
}
}
}
}
Yes, before filling your list array, clear your arraylist so last added data will be void and eveytime it will load new data in your arraylist.
Note: An arraylist which you filling before passing to adapter to fill your listview. Hope it make sense
I was searching on google and stackover could not find the exact solution.
My problem is that, I have a ArrayList<String> adapter and it has
Gatewick London England
Ory Paris France
Heathrow London England
If user enters "Lon" into AutoCompleteTextView then I have to display line number 1 and 3. Because these have London string.
I tried this link and i pasted code here but it gives warning on line #57
String prefix = constraint.toString().toLowerCase();
PkmnAdapter
public class PkmnAdapter extends ArrayAdapter<String> {
private ArrayList<Pkmn> original;
private ArrayList<Pkmn> fitems;
private Filter filter;
public PkmnAdapter(Context context, int textViewResourceId,
ArrayList<Pkmn> items) {
super(context, textViewResourceId);
this.original = new ArrayList<Pkmn>(items);
this.fitems = new ArrayList<Pkmn>(items);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
}
Pkmn pkmn = fitems.get(position);
if (pkmn != null) {
TextView tt = (TextView) v.findViewById(R.id.RlabPName);
if (tt != null) {
tt.setText(pkmn.getNAME());
}
}
return v;
}
#Override
public Filter getFilter() {
if (filter == null)
filter = new PkmnNameFilter();
return filter;
}
private class PkmnNameFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
String prefix = constraint.toString().toLowerCase();
if (prefix == null || prefix.length() == 0) {
ArrayList<Pkmn> list = new ArrayList<Pkmn>(original);
results.values = list;
results.count = list.size();
} else {
final ArrayList<Pkmn> list = new ArrayList<Pkmn>(original);
final ArrayList<Pkmn> nlist = new ArrayList<Pkmn>();
int count = list.size();
for (int i = 0; i < count; i++) {
final Pkmn pkmn = list.get(i);
final String value = pkmn.getNAME().toLowerCase();
if (value.startsWith(prefix)) {
nlist.add(pkmn);
}
}
results.values = nlist;
results.count = nlist.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
fitems = (ArrayList<Pkmn>) results.values;
clear();
if (fitems != null) {
int count = fitems.size();
for (int i = 0; i < count; i++) {
Pkmn pkmn = (Pkmn) fitems.get(i);
fitems.add(pkmn);
}
}
}
}
}
MainActivity.java to put adapter
Pkmn[] item = new Pkmn[4];
item[0] = new Pkmn("Gatewick London England");
item[1] = new Pkmn("Ory Paris France");
item[2] = new Pkmn("Heathrow London England");
item[3] = new Pkmn("Ataturk Istanbul Turkey");
ArrayList<Pkmn> list = new ArrayList<Pkmn>(Arrays.asList(item));
MultiAutoCompleteTextView auto = (MultiAutoCompleteTextView) findViewById(R.id.multiAutoCompleteTextView1);
PkmnAdapter adap = new PkmnAdapter(this,android.R.layout.simple_list_item_1, list);
First of all, if you enter "Lon" you should not check if the elements start with "Lon". Probably you need to switch the if statement to:
if (value.contains(prefix)) {
nlist.add(pkmn);
}
Before you perform any filtering in your performFiltering() method check if the constraint is null (Hint: use TextUtils class). If so, then return original data. Therefore you are avoiding NPE. You also need to pay attention to critical points where NPE can be thrown like this one:
if (prefix == null || prefix.length() == 0) { }
Cheers,