9

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

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
user405821
  • 1,845
  • 2
  • 16
  • 17
  • i think if you just use myCursor.moveToNext() you would put it at the beginning of the while loop like while(myCursor.moveToNext()){...}. You start before the 1st position so there is no contradiction. – jkhouw1 May 13 '11 at 00:11
  • The book's author disagrees with your method. They claim you need the if(myCursor.moveToFirst()) for the case where the result set is empty. If you start with a MoveToNext(), then the cursor's location is undefined and your code will always fail. – user405821 May 13 '11 at 01:11
  • 1
    well i guess he's probably right. i just did a quick search through my code and i actually always did do the movetofirst then a do{}while() loop. still doesn't necessarily imply a contradiction - it could be the cursor doesn't even have a starting location defined until you do a moveto a specific entry (either first or whatever). – jkhouw1 May 13 '11 at 01:37
  • I think you've missed the contradiction. The author claims you can test the result from moveToNext() if you are "AT" the last entry, which if true, is a contradiction of the documentation's claim it will return false if "already PAST". – user405821 May 13 '11 at 02:40

5 Answers5

21

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.

Community
  • 1
  • 1
orip
  • 73,323
  • 21
  • 116
  • 148
7

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;
    }
Aleadam
  • 40,203
  • 9
  • 86
  • 108
  • @user Well, that only if you are really strict with the wording, consider that "if the cursor is already past the last entry" refers to the moment before the change and not after, and if you do not take into account the part I quoted, which is the only one important: "Returns: whether the move succeeded." – Aleadam May 13 '11 at 03:33
  • 2
    "Already past" means before the call, not after, otherwise we have to redefine the English word "already". Strict meaning is essential in documentation because ambiguous words are at best, NOT helpful. "Returns: whether the move succeeded.", means nothing because succeeded isn't defined anywhere I can see and moving one beyond the end of a data set is usually defined as success. I suggest simply stating the documentation is wrong and suggesting it be fixed. – user405821 May 13 '11 at 06:42
2

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.

2

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.

BryceCicada
  • 265
  • 2
  • 7
0

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."

Patrick
  • 165
  • 2
  • 10