At the moment I need to filter a Cursor/CursorAdapter to only show rows that match a specific condition in the ListView. I don't want to requery the db all the time. I just want to filter the Cursor I got from querying the DB.
I have seen the question: Filter rows from Cursor so they don't show up in ListView
But I don't understand how to do the filtering by overwritting the "move" methods in my CursorWrapper. An example would be nice.
Thank you very much.
UPDATE:
I have rewritten the source and my employer has made it available as open source software: https://github.com/clover/android-filteredcursor
You don't need to override all the move methods in CursorWrapper, you do need to override a bunch though due to the design of the Cursor interface. Let's pretend you want to filter out row #2 and #4 of a 7 row cursor, make a class that extends CursorWrapper and override these methods like so:
private int[] filterMap = new int[] { 0, 1, 3, 5, 6 };
private int mPos = -1;
@Override
public int getCount() { return filterMap.length }
@Override
public boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
final int realPosition = filterMap[position];
// When moving to an empty position, just pretend we did it
boolean moved = realPosition == -1 ? true : super.moveToPosition(realPosition);
if (moved) {
mPos = position;
} else {
mPos = -1;
}
return moved;
}
@Override
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
@Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
@Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
@Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
@Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
@Override
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
@Override
public final boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
}
return mPos == -1;
}
@Override
public final boolean isAfterLast() {
if (getCount() == 0) {
return true;
}
return mPos == getCount();
}
@Override
public int getPosition() {
return mPos;
}
Now the interesting part is creating the filterMap, that's up to you.
I was looking for something similar, in my case I wanted to filter items based on a string comparision. I found this gist https://gist.github.com/ramzes642/5400792, which works fine unless you start playing around with the position of the cursor. So I found satur9nine answer, his one respects the position api but just needs some adjustments for filtering based on cursor, so I merged the two. You can change your code to fit it: https://gist.github.com/rfreitas/ab46edbdc41500b20357
import java.text.Normalizer;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.util.Log;
//by Ricardo [email protected]
//ref: https://gist.github.com/ramzes642/5400792 (the position retrieved is not correct)
//ref: http://stackoverflow.com/a/7343721/689223 (doesn't do string filtering)
//the two code bases were merged to get the best of both worlds
//also added was an option to remove accents from UTF strings
public class FilterCursorWrapper extends CursorWrapper {
private static final String TAG = FilterCursorWrapper.class.getSimpleName();
private String filter;
private int column;
private int[] filterMap;
private int mPos = -1;
private int mCount = 0;
public FilterCursorWrapper(Cursor cursor,String filter,int column) {
super(cursor);
this.filter = deAccent(filter).toLowerCase();
Log.d(TAG, "filter:"+this.filter);
this.column = column;
int count = super.getCount();
if (!this.filter.isEmpty()) {
this.filterMap = new int[count];
int filteredCount = 0;
for (int i=0;i<count;i++) {
super.moveToPosition(i);
if (deAccent(this.getString(this.column)).toLowerCase().contains(this.filter)){
this.filterMap[filteredCount] = i;
filteredCount++;
}
}
this.mCount = filteredCount;
} else {
this.filterMap = new int[count];
this.mCount = count;
for (int i=0;i<count;i++) {
this.filterMap[i] = i;
}
}
this.moveToFirst();
}
public int getCount() { return this.mCount; }
@Override
public boolean moveToPosition(int position) {
Log.d(TAG,"moveToPosition:"+position);
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
final int realPosition = filterMap[position];
// When moving to an empty position, just pretend we did it
boolean moved = realPosition == -1 ? true : super.moveToPosition(realPosition);
if (moved) {
mPos = position;
} else {
mPos = -1;
}
Log.d(TAG,"end moveToPosition:"+position);
return moved;
}
@Override
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
@Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
@Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
@Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
@Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
@Override
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
@Override
public final boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
}
return mPos == -1;
}
@Override
public final boolean isAfterLast() {
if (getCount() == 0) {
return true;
}
return mPos == getCount();
}
@Override
public int getPosition() {
return mPos;
}
//added by Ricardo
//ref: http://stackoverflow.com/a/22612054/689223
//other: http://stackoverflow.com/questions/8523631/remove-accents-from-string
//other: http://stackoverflow.com/questions/15190656/easy-way-to-remove-utf-8-accents-from-a-string
public static String deAccent(String str) {
//return StringUtils.stripAccents(str);//this method from apache.commons respects chinese characters, but it's slower than flattenToAscii
return flattenToAscii(str);
}
//ref: http://stackoverflow.com/a/15191508/689223
//this is the fastest method using the normalizer found yet, the ones using Regex are too slow
public static String flattenToAscii(String string) {
char[] out = new char[string.length()];
string = Normalizer.normalize(string, Normalizer.Form.NFD);
int j = 0;
for (int i = 0, n = string.length(); i < n; ++i) {
char c = string.charAt(i);
int type = Character.getType(c);
if (type != Character.NON_SPACING_MARK){
out[j] = c;
j++;
}
}
return new String(out);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With