Question

I'm querying the CallLog content provider and need to detect the column types.

In Honeycomb and newer (API Level 11+) you can get a columns preferred data type by calling the method Cursor.getType(int columnIndex) which returns one of the following types:

  • FIELD_TYPE_NULL (0)
  • FIELD_TYPE_INTEGER (1)
  • FIELD_TYPE_FLOAT (2)
  • FIELD_TYPE_STRING (3)
  • FIELD_TYPE_BLOB (4)

How can I accomplish this on pre-Honeycomb <11 devices?

I've tried the following:

for ( int i = 0; i < cursor.getColumnCount(); i++ ) {    

    int columnType = -1;
    try {
        cursor.getInt( i );
        columnType = Cursor.FIELD_TYPE_INTEGER;

    } catch ( Exception ignore ) {

        try {
            cursor.getString( i );
            columnType = Cursor.FIELD_TYPE_STRING;

        } catch ( Exception ignore1 ) {

            try {
                cursor.getFloat( i );
                columnType = Cursor.FIELD_TYPE_FLOAT;

            } catch ( Exception ignore2 ) {

                try {                                             
                  cursor.getBlob( i );
                  columnType = Cursor.FIELD_TYPE_BLOB;

                } catch ( Exception ignore3 ) {

                     columnType = Cursor.FIELD_TYPE_NULL;
                }
           }
       }
   }

}

However, no exception is thrown. The data is always casted in the first type you are checking for, in this case getInt(). That means, I get the correct values if the column type is Integer but a 0 for all other types.

Why am I not looking in the documentation to check what type is stored? The columns differ depending on the device manufacturer and not all of them are documented, see this question: How to handle manufacturer-dependent differences in ContentProviders?

Any ideas?

Was it helpful?

Solution

You can use this code when cursor is positioned in a valid row:

CursorWrapper cw = (CursorWrapper)cursor;

Class<?> cursorWrapper = CursorWrapper.class;
Field mCursor = cursorWrapper.getDeclaredField("mCursor");
mCursor.setAccessible(true);
AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
int pos = abstractWindowedCursor.getPosition();
for ( int i = 0; i < cursor.getColumnCount(); i++ ) {
    String type = null;
    if (cursorWindow.isNull(pos, i)) {
        type = "Cursor.FIELD_TYPE_NULL";
    } else if (cursorWindow.isLong(pos, i)) {
        type = "Cursor.FIELD_TYPE_INTEGER";
    } else if (cursorWindow.isFloat(pos, i)) {
        type = "Cursor.FIELD_TYPE_FLOAT";
    } else if (cursorWindow.isString(pos, i)) {
        type = "Cursor.FIELD_TYPE_STRING";
    } else if (cursorWindow.isBlob(pos, i)) {
        type = "Cursor.FIELD_TYPE_BLOB";
    }
}

Note that Cursor.FIELD_TYPE_* constant values are defined starting from HONEYCOMB.

OTHER TIPS

Expanding on Juan's answer, here is my replacement for the API 11 method Cursor.getType(int i) - for a cursor retuned by an SQL query

public class DbCompat {

    protected static final int FIELD_TYPE_BLOB = 4;
    protected static final int FIELD_TYPE_FLOAT = 2;
    protected static final int FIELD_TYPE_INTEGER = 1;
    protected static final int FIELD_TYPE_NULL = 0;
    protected static final int FIELD_TYPE_STRING = 3;

    static int getType(Cursor cursor, int i) throws Exception {
        SQLiteCursor sqLiteCursor = (SQLiteCursor) cursor;
        CursorWindow cursorWindow = sqLiteCursor.getWindow();
        int pos = cursor.getPosition();
        int type = -1;
        if (cursorWindow.isNull(pos, i)) {
            type = FIELD_TYPE_NULL;
        } else if (cursorWindow.isLong(pos, i)) {
            type = FIELD_TYPE_INTEGER;
        } else if (cursorWindow.isFloat(pos, i)) {
            type = FIELD_TYPE_FLOAT;
        } else if (cursorWindow.isString(pos, i)) {
            type = FIELD_TYPE_STRING;
        } else if (cursorWindow.isBlob(pos, i)) {
            type = FIELD_TYPE_BLOB;
        }

        return type;
    }
}

gist: https://gist.github.com/kassim/c340cbfc5243db3a4826

There is something that may work : http://developer.android.com/reference/android/database/DatabaseUtils.html cursorRowToContentValues

will copy the row in a ContentValues object. Then, you can call ContentValues.get(), which give you an object. you can then look at the Class of this object.

edit

According to the source code of DatabaseUtils, the object are either blobs or Strings.

edit 2

However, if your cursor is a WindowedCursor, it has methods to know the object types. (isBlob, isString, isLong...)

I faced the same problem in the past. I tackled it with a pretty nice solution. Match this with your needs. In my case I have an amount of different objects which are all synced to a server in the cloud. They all have common properties, so that they all inherit from a common BaseObject. This object has a method that takes a cursor as a parameter and returns a new object of the same type, so that each object that inherits from it, overrides this method with the extended properties of it.

*Note that the inheritance of objects is not necessary for this approach. It's just a smarter way of doing it. As long as you have the same method in all the objects you need to take form DB this will work as you'll be able to see in the end.

Let me illustrate that:

Our baseObject.

public class BaseObject{

    protected int number;
    protected String text;

    public <T extends BaseObject> T setObject(Cursor c) {
        number = c.getInt(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_NUMBER));
        text = c.getString(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_TEXT));

        return (T) this;
    }
}

A new object that inherits from the first.

public class Contact extends BaseObject{

    private String name;

    @Override
    public <T extends BaseObject> T setObject(Cursor c) {

        super.setObject(c);

        name = c.getString(cur.getColumnIndexOrThrow(COLUMN_NAME_FOR_NAME));

        return (T) this;
    }
}

Finally in your database it's as easy as to ask for the data you want by calling a generic method "getAllObjects" and passing the class type you want to query along with the other parameters of the query:

public synchronized <T extends BaseObject> ArrayList<T> getObjectsForClass(final Class<T> classType,
        String selection, String[] selectionArgs, String sort, String limit) {

    ArrayList<T> objects = null;

    if (db == null || !db.isOpen()) {
        db = getWritableDatabase();
    }

    objects = new ArrayList<T>();

    Cursor c = null;
    T object;
    try {
        object = classType.newInstance();

        String table = object.getTable();

        StringBuilder tableSb = new StringBuilder();
        tableSb.append(table).append(" INNER JOIN ").append(Constants.DB_BASE_OBJECT_TABLE)
                .append(" ON ").append(table).append(".").append(BaseObject.DB_OBJECT_ID_KEY).append(" = ")
                .append(Constants.DB_BASE_OBJECT_TABLE).append(".")
                .append(BaseObject.DB_ID_KEY);

        c = db.query(tableSb.toString(), null, selection, selectionArgs, null, null, sort, limit);

        if (c.getCount() > 0) {
            c.moveToFirst();
            while (!c.isAfterLast()) {

                object = classType.newInstance();
                object.setObject(c);
                objects.add(object);

                c.moveToNext();
            }
        }

    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    c.close();

    return objects;
}

And there you go. One generic method to get any object from the your database and to successfully turn it into an object or an array of objects in runtime.

Notes:

  • Every object should have a method getTable() in order to be able to query the right table
  • In this approach there's also a OODB connection as you may see. You can use the same approach without that, by just querying all items (SELECT * FROM...)

Hope it helps. Answer back with issues or doubts.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top