Home » Android » sqlite – Is Android Cursor.moveToNext() Documentation Correct?

sqlite – Is Android Cursor.moveToNext() Documentation Correct?

Posted by: admin June 15, 2020 Leave a comment

Questions:

boolean android.database.Cursor.moveToNext() documentation says:

http://developer.android.com/reference/android/database/Cursor.html#moveToNext%28%29

Move the cursor to the next row.

This method will return false if the cursor is already past the last entry in the result set.


However, my book says to do the following to extract data from a cursor:

Cursor myCursor = myDatabase.query(...);
if (myCursor.moveToFirst()) {
    do {
    int value = myCursor.getInt(VALUE_COL);
    // use value
    } while (myCursor.moveToNext());
}

Who’s right? These both can’t be true. If you can’t see the contradiction, imagine myCursor has 1 row returned from the query. The first call to getInt() will work, but then moveToNext() will return true because it is not “already” past the last entry in the result set. So now the cursor will be past the last entry and the second call to getInt() will do something undefined.

I suspect the documentation is wrong and should instead read:

This method will return false if the cursor is “already at” the last entry in the result set.

Must the cursor be already PAST (not AT) the last entry before the moveToNext() method returns false?

No Snark Please

How to&Answers:

Verbatim from the API:

Returns:
whether the move succeeded.


So, it means that:

Cursor in first row -> moveToNext() -> cursor in second row -> there’s no second row -> return false


If you want the details, go to the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/database/AbstractCursor.java#AbstractCursor.moveToNext%28%29

public final boolean moveToNext() {
  return moveToPosition(mPos + 1);
}

public final 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;
    }

Answer:

A simpler idiom is:

Cursor cursor = db.query(...);
while (cursor.moveToNext()) {
    // use cursor
}

This works because the initial cursor position is -1, see the Cursor.getPosition() docs.

You can also find usages of cursors in the Android source code itself with this Google Code Search query. Cursor semantics are the same in SQLite database and content providers.

References: this question.

Answer:

I think I tend to stay away from solutions that are based on a hidden assumption like: I hope that sqlite never changes it api and that a cursor will always start at the first item.

Also, I have almost always been able to replace a while statement with a for statement. So my solution, shows what I expect the cursor to start at, and avoids using a while statement:

for( boolean haveRow = c.moveToFirst(); haveRow; haveRow = c.moveToNext() ) {
...
}

why is showing that a cursor needs to start at the first row, well 6 months down the line you might be debugging your own code, and will wonder why you didn’t make that explicit so you could easily debug it.

Answer:

It appears to be down to the Android implementation of AbstractCursor and it remains broken in Jellybean.

I implemented the following unit test to demonstrate the problem to myself using a MatrixCursor:

@Test
public void testCursor() {
  MatrixCursor cursor = new MatrixCursor(new String[] { "id" });
  for (String s : new String[] { "1", "2", "3" }) {
    cursor.addRow(new String[] { s });
  }

  cursor.moveToPosition(0);
  assertThat(cursor.moveToPrevious(), is(true));

  cursor.moveToPosition(cursor.getCount()-1);
  assertThat(cursor.moveToNext(), is(true));

  assertThat(cursor.moveToPosition(c.getCount()), is(true));
  assertThat(cursor.moveToPosition(-1), is(true));
}

All assertions fail, contrary to the documentation for moveToNext, moveToPrevious and moveToPosition.

Reading the code at API 16 for AbstractCursor.moveToPosition(int position) it appears to be intentional behaviour, ie the methods explicitly return false in these cases, contrary to the documentation.

As a side note, since the Android code sat on existing devices in the wild cannot be changed, I have taken the approach of writing my code to match the behaviour of the existing Android implementation, not the documentation. ie. When implementing my own Cursors / CursorWrappers, I override the methods and write my own javadoc describing the departure from the existing documentation. This way, my Cursors / CursorWrappers remain interchangeable with existing Android cursors without breaking run-time behaviour.

Answer:

Cursor.moveToNext() returning a boolean is only useful if it will not move the cursor past the last entry in the data set. Thus, I have submitted a bug report on the documentation’s issue tracker.

https://issuetracker.google.com/issues/69259484

It reccomends the following sentence:
“This method will return false if the current (at time of execution) entry is the last entry in the set, and there is no next entry to be had.”