Cursors
Cursors are what contain the result set of a query made against a database in Android. The Cursor class has an API that allows an app to read (in a type-safe manner) the columns that were returned from the query as well as iterate over the rows of the result set.
Reading Cursor Data
Once a cursor has been returned from a database query, an app needs to iterate over the result set and read the column data from the cursor. Internally, the cursor stores the rows of data returned by the query along with a position that points to the current row of data in the result set. When a cursor is returned from a query() method, its position points to the spot before the first row of data. This means that before any rows of data can be read from the cursor, the position must be moved to point to a valid row of data.
The Cursor class provides the following methods to manipulate its internal position:
boolean Cursor.move(int offset): Moves the position by the given offset
boolean Cursor.moveToFirst(): Moves the position to the first row
boolean Cursor.moveToLast(): Moves the position to the last row
boolean Cursor.moveToNext(): Moves the cursor to the next row relative to the current position
boolean Cursor.moveToPosition(int position): Moves the cursor to the specified position
Cursor.moveToPrevious(): Moves the cursor to the previous row relative to the current position
Each move() method returns a boolean to indicate whether the operation was successful or not. This flag is useful for iterating over the rows in a cursor.
Listing 5.10 shows the code to read data from a cursor containing all the data from the people table.
Listing 5.10 Reading Cursor Data
SQLiteDatabase db = getDatabase(); String[] columns = {"first_name", "last_name", "id"}; Cursor cursor = db.query("people", columns, null, null, null, null, null); while(cursor.moveToNext()) { int index; index = cursor.getColumnIndexOrThrow("first_name"); String firstName = cursor.getString(index); index = cursor.getColumnIndexOrThrow("last_name"); String lastName = cursor.getString(index); index = cursor.getColumnIndexOrThrow("id"); long id = cursor.getLong(index); //... do something with data }
The code in Listing 5.10 uses a while loop to iterate over the rows in the cursor returned from the query() method. This pattern is useful if the code performing the iteration “controls” the cursor and has sole access to it. If other code can access the cursor (for example, if the cursor is passed into a method as a parameter), the cursor should also be set to a known position as the current position may not be the position ahead of the first row.
Once the cursor’s position is pointing to a valid row, the columns of the row can be read from the cursor. To read the data, the code in Listing 5.10 uses two methods from the cursor class: Cursor.getColumnIndexOrThrow() and one of the type get() methods from the Cursor class.
The Cursor.getColumnIndexOrThrow() method takes a String parameter that indicates which column to read from. This String value needs to correspond to one of the strings in the columns parameter that was passed to the query() method. Recall that the columns parameter determines what table columns are part of the result set. Cursor.getColumnIndexOrThrow()throws an exception if the column name does not exist in the cursor. This usually indicates that the column was not part of the columns parameter of the query(). The Cursor class also contains a Cursor.getColumnIndex() method that does not throw an exception if the column name is not found. Instead, Cursor.getColumnIndex() returns a -1 value to represent an error.
Once the column index is known, it can be passed to one of the cursor’s get() methods to return the typed data of the row. The get() methods return the data from the column in the row which can then be used by the app. The Cursor class contains the following methods for retrieving data from a row:
byte[] Cursor.getBlob(int columnIndex): Returns the value as a byte[]
double Cursor.getDouble(int columnIndex): Returns the value as a double
float Cursor.getFloat(int columnIndex): Returns the value as a float
int Cursor.getInt(int columnIndex): Returns the value as an int
long Cursor.getLong(int columnIndex): Returns the value as a long
short Cursor.getShort(int columnIndex): Returns the value as a short
String Cursor.getString(int columnIndex): Returns the value as a String
Managing the Cursor
The internals of a cursor can contain a lot of resources such as all the data returned from the query along with a connection to the database. Because of this, it is important to handle a cursor appropriately and tell it to clean up when it is no longer in use to prevent memory leaks. To perform the cleanup, the Cursor class contains the Cursor.close() method, which needs to be called when an activity or fragment no longer needs the cursor.
In versions of Android before 3.0, cursor maintenance was left to developers. They either had to handle the closing of the cursor themselves or had to make sure they informed an activity that it was using a cursor so the activity would close the cursor at an appropriate time.
Android 3.0 introduced the loader framework that takes care of managing cursors for activities/fragments. To support older versions of Android, the loader framework has also been backported and added to the support library. When using the loader framework, apps no longer need to worry about calling Cursor.close() or informing an activity/fragment of a cursor that it needs to manage.