Android: How to create my own cursor class?
Asked Answered
T

5

6

I'm using Sqlite in Android and to get a value from the database I use something like this:

Cursor cursor = sqliteDatabase.rawQuery("select title,category from table", null);

int columnIndexTitle = cursor.getColumnIndex("title");
iny columnIndexCategory = cursor.getColumnIndex("category");

cursor.moveToFirst();
while (cursor.moveToNext()) {
    String title = cursor.getString(columnIndexTitle);  
    String category = cursor.getString(columnIndexCategory);    
}
cursor.close();

I want to create my own Cursor so that I can do getColumnIndex() and getString() with one method. Something like this:

String title = cursor.getString("title");

I want to create my own class that extends the cursor that I get from sqliteDatabase.rawQuery, but I'm not sure how to accomplish this. Should I extend SQLiteCursor or how should I do this? Is it even a possible and is it a good idea?

Torrie answered 12/4, 2011 at 18:24 Comment(1)
Do you have multiple rows? Creating your own getString will cause a map lookup for each call instead of only for getColumnIndex.Amathiste
A
3

Creating your own getString will cause a map lookup for each call instead of only for getColumnIndex.

Here's the code for SQLiteCursor.getColumnIndex and AbstractCursor.getColumnIndex. If you have many rows, reducing calls to this function will prevent unnecessary string processing and map lookups.

Amathiste answered 12/4, 2011 at 19:2 Comment(0)
I
4

I came across this question looking for the best way to create a custom Cursor to use together with a SQLiteDatabase. In my case I needed an extra attribute to the Cursor to carry an additional piece of information, so my use case is not exactly as in the body of the question. Posting my findings in hope it will be helpful.

The tricky part for me was that the SQLiteDatabase query methods returns a Cursor, and I needed to pass on a custom subclass to Cursor.

I found the solution in the Android API: Use the CursorWrapper class. It seems to be designed exactly for this.

The class:

public class MyCustomCursor extends CursorWrapper {

    public MyCustomCursor(Cursor cursor) {
        super(cursor);
    }

    private int myAddedAttribute;

    public int getMyAddedAttribute() {
        return myAddedAttribute;
    }

    public void setMyAddedAttribute(int myAddedAttribute) {
        this.myAddedAttribute = myAddedAttribute;
    }

}

Usage:

public MyCustomCursor getCursor(...) {
    SQLiteDatabase DB = ...;
    Cursor rawCursor = DB.query(...);
    MyCustomCursor myCursor = new MyCustomCursor(rawCursor);
    myCursor.setMyAddedAttribute(...);
    return myCursor;
}
Iqbal answered 10/11, 2014 at 13:9 Comment(0)
A
3

Creating your own getString will cause a map lookup for each call instead of only for getColumnIndex.

Here's the code for SQLiteCursor.getColumnIndex and AbstractCursor.getColumnIndex. If you have many rows, reducing calls to this function will prevent unnecessary string processing and map lookups.

Amathiste answered 12/4, 2011 at 19:2 Comment(0)
S
2

I wouldn't extend it, I'd make a helper:

class MartinCursor {
    private Cursor cursor;

    MartinCursor(Cursor cursor) {
        this.cursor = cursor;
    }

    String getString(String column) {
        ....
    }
}

or

class MartinCursorHelper {
    static String getString(Cursor cursor, String column) {
        ....
    }
}

Personally, I'd do the latter, unless you hate providing this extra argument all the time.

EDIT: I forgot to mention pydave's important point: If you call this in a loop, you're setting yourself up for a noticeable performance impact. The preferred way is to lookup the index once, cache it, and use that instead.

Stretchy answered 12/4, 2011 at 18:31 Comment(2)
Thanks! This seems like a solution that will work. But is it possible to extend it instead and not use a helper?Torrie
Not really, since the object is instantiated by the operating system, not by you, and there's no way to create a subclassed Cursor from another one.Stretchy
J
0

You should make use of the DatabaseUtils.stringForQuery() static method that is already in Android SDK to easily retrieve a value, this example is for String bot there is also method for Long

stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)

Utility method to run the query on the db and return the value in the first column of the first row.

Something like

String myString=DatabaseUtils.stringForQuery(getDB(),query,selectionArgs);
Jessie answered 12/4, 2011 at 18:29 Comment(1)
Oh, sorry this wasn't what I needed. My example was maybe a little to simple, I added some code to show what I wanted.Torrie
W
0

Came across this looking for a different solution, but just want to add this since I believe the answers are unsatisfactory.

You can easily create your own cursor class. In order to allow functions requiring Cursor to accept it, it must extend AbstractCursor. To overcome the issue of system not using your class, you simply make your class a wrapper.

There is a really good example here. https://android.googlesource.com/platform/packages/apps/Contacts/+/8df53636fe956713cc3c13d9051aeb1982074286/src/com/android/contacts/calllog/ExtendedCursor.java

public class ExtendedCursor extends AbstractCursor {
/** The cursor to wrap. */
private final Cursor mCursor;
/** The name of the additional column. */
private final String mColumnName;
/** The value to be assigned to the additional column. */
private final Object mValue;
/**
 * Creates a new cursor which extends the given cursor by adding a column with a constant value.
 *
 * @param cursor the cursor to extend
 * @param columnName the name of the additional column
 * @param value the value to be assigned to the additional column
 */
public ExtendedCursor(Cursor cursor, String columnName, Object value) {
    mCursor = cursor;
    mColumnName = columnName;
    mValue = value;
}
@Override
public int getCount() {
    return mCursor.getCount();
}
@Override
public String[] getColumnNames() {
    String[] columnNames = mCursor.getColumnNames();
    int length = columnNames.length;
    String[] extendedColumnNames = new String[length + 1];
    System.arraycopy(columnNames, 0, extendedColumnNames, 0, length);
    extendedColumnNames[length] = mColumnName;
    return extendedColumnNames;
}

That's the general idea of how it will work.

Now to the meat of the problem. To prevent the performance hit, create a hash to hold the column indices. This will serve as a cache. When getString is called, check the hash for the column index. If it does not exist, then fetch it with getColumnIndex and cache it.

I'm sorry I can't add any code currently, but I'm on mobile so I'll try to add some later.

Weigela answered 28/9, 2014 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.