Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering a cursor the right way?

Tags:

android

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.

like image 506
denis Avatar asked Sep 22 '10 06:09

denis


2 Answers

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.

like image 85
satur9nine Avatar answered Nov 13 '22 10:11

satur9nine


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);
    }
}
like image 35
Ricardo Freitas Avatar answered Nov 13 '22 09:11

Ricardo Freitas