android-sqliteandroid-contentproviderandroid-loader

Exception: (Index -1 requested, with a size of 2) while retrieving data from a Cursor


Hey I have a problem with accessing the data from a Cursor in Android Studio. I have two tables in the database events and messages. So each event in the events table may have multiple messages in the messages table. The event and its messages share a common event_id. I am using LoaderCallbacks to get a Cursor from a ContentProvider. In the ContentProvider query method, I am using a rawQuery to INNER JOIN these two tables.

In this example I query for an event which shares its event_id with two messages from the message table. In the ContentProvider the rawQuery() method with the INNER JOIN was performed. At this state I can see in the Debugger that the variable mCount from the Cursor is -1. I am not sure what this means but anyways, back in the onLoadFinish() method the Cursor now contains a variable mCursor and this variable again contains a variable mCount which is 2. Which makes sense, since the query should return a Cursor with two rows and since there are two messages. But now I try to get a String with the getString() method and the CursorIndexOutOfBoundsException: Index -1 requested, with a size of 2 is thrown. I realy don't understand what this means. And I don't find any hint in die Cursor docs, what it means when mCount is -1. Furthermore in the onLoadFinished method the variable mEditTable is null. And in other queries where I don't join anything it always contains a table.

I am quite sure that the issue somwhere lays with the INNER JOIN, since everything works fine if I use just a query method inside the ContentProvider.query method. But I don't know what I should do differently. I hope you might help me with this. I am aware that, if there is no message related to an event, this query will return an empty cursor with mCount = 0 and then again an exception will be thrown. But this is not the case in this example.

EventProvider class:

public class EventProvider extends ContentProvider {

private static final String LOG_TAG = EventProvider.class.getSimpleName();


private static final int EVENTS = 100;

private static final int EVENT_ID = 101;

private static final int EVENT_MESSAGE_ID = 102;

private static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

private EventDbHelper mEventDbHelper;

static {
    sUriMatcher.addURI(EventContract.CONTENT_AUTHORITY, EventContract.PATH_EVENTS, EVENTS);
    sUriMatcher.addURI(EventContract.CONTENT_AUTHORITY, EventContract.PATH_EVENTS + "/#", EVENT_ID);
    sUriMatcher.addURI(EventContract.CONTENT_AUTHORITY, EventContract.PATH_EVENT_AND_ITS_MESSAGES + "/#", EVENT_MESSAGE_ID);
}


@Override
public boolean onCreate() {
    mEventDbHelper = new EventDbHelper(getContext());
    return true;
}


@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {

    SQLiteDatabase database = mEventDbHelper.getReadableDatabase();

    Cursor retCursor;

    final int match = sUriMatcher.match(uri);
    switch (match) {
        case EVENTS:
            retCursor = database.query(EventEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
            break;
        case EVENT_ID:
            selection = EventEntry.COLUMN_EVENT_ID + "=?";
            selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
            retCursor = database.query(EventEntry.TABLE_NAME, projection,selection, selectionArgs, null, null, sortOrder);
            break;
        case EVENT_MESSAGE_ID:
            selection = MessageEntry.TABLE_NAME + "." + MessageEntry.COLUMN_EVENT_ID + "=?";
            selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};

            StringBuilder sqlQuery = new StringBuilder();
            sqlQuery.append("SELECT ");
            if(projection != null && projection.length > 0) {
                for(String s: projection) {
                    sqlQuery.append(s).append(", ");
                }
            }
            sqlQuery.replace(sqlQuery.length()-2, sqlQuery.length(), " FROM ").append(MessageEntry.TABLE_NAME);
            sqlQuery.append(" INNER JOIN ").append(EventEntry.TABLE_NAME).append(" ON ");
            sqlQuery.append(EventEntry.TABLE_NAME).append(".").append(EventEntry.COLUMN_EVENT_ID).append("=");
            sqlQuery.append(MessageEntry.TABLE_NAME).append(".").append(MessageEntry.COLUMN_EVENT_ID);
            sqlQuery.append(" WHERE ").append(selection).append(" ORDER BY ").append(sortOrder).append(";");

            Log.v(LOG_TAG, "SQL command for: " + uri.toString());
            Log.v(LOG_TAG, sqlQuery.toString());

            retCursor = database.rawQuery(sqlQuery.toString(), selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Cannot query unknown URI: " + uri);
    }

    retCursor.setNotificationUri(getContext().getContentResolver(), uri);
    return retCursor;
}

EventActivity:

@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {

    String[] projection = {
            MessageEntry.TABLE_NAME + "." + MessageEntry.COLUMN_EVENT_ID,
            MessageEntry.TABLE_NAME + "." + MessageEntry.COLUMN_SENDER,
            MessageEntry.TABLE_NAME + "." + MessageEntry.COLUMN_DATE,
            MessageEntry.TABLE_NAME + "." + MessageEntry.COLUMN_MESSAGE,
            EventEntry.TABLE_NAME + "." + EventEntry._ID,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_NAME,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_DATE,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_DATE_ADDENDUM,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_CONTACT,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_STATUS,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_PICTURE_NAME,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_DESCRIPTION,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_STREET,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_HOUSE_NUMBER,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_POST_CODE,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_CITY,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_EMAIL,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_LOCATION,
            EventEntry.TABLE_NAME + "." + EventEntry.COLUMN_SIGNED_UP
    };

    String sortOrder = MessageEntry.COLUMN_DATE + " DESC";
    return new CursorLoader(this, mCurrentEventUri, projection, null, null, sortOrder);
}


@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {

    int indexName = cursor.getColumnIndex(EventEntry.COLUMN_NAME);
    int indexImage = cursor.getColumnIndex(EventEntry.COLUMN_PICTURE_NAME);
    String imageName = cursor.getString(indexImage); //Here the Exception is thrown

    mEventName.setText(cursor.getString(indexName));


   mEventAdapter.swapCursor(cursor);
}

If you need something else, I will upload it. Thanks.


Solution

  • CursorIndexOutOfBoundsException: Index -1 requested, with a size of 2

    -1 with Cursors will generally be 1 of 2 issues :-

    1. that the column name passed to getColumnIndex is not a name of a column in the output.
    2. that the Cursor is at the beginning that is at the position that is "before the first row", as is the case when a Cursor is returned.

    I suspect that your issue is due to 2 as I believe the message indicates the row of the column.

    2 can happen if a move???? (e.g. moveToFirst, moveToNext) method was not actioned (I cannot see any such move in your code) or that the result was not checked (the move methods return true or false to indicate whether or not the move request could be satisfied).

    The fix would be to move the Cursor to a row, checking if the move was actually successful and to then extract the data.

    e.g.

    String imageName = "No Image"
    if (cursor.moveToFirst() && indexImage > -1) {    
        imageName = cursor.getString(indexImage); //Here the Exception is thrown
    }