Search code examples
androidsqlcursor

Update database using Content Provider


I have a listview with some elements that are saved on a database and I access them using a custom content provider.

In my main activity, I have implemented a ResourceCursorAdapter.

When I long click on a element of the list, I get a context menu where I have the edit-update options.

The edit option starts other activity with some edittexts where I should be able to update the selected item's values (I use this activity too to create a new item, and I do this right).

The problem I'm getting is that I'm not getting the items updated nor deleted, so i think that I'm not mannaging right the ID to access the database. This is my code:

Custom Content Provider - Update and delete methods

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    int count = 0;
    database = mDbHelper.getWritableDatabase();
    int match = mUriMatcher.match(uri);

    switch (match){
        case URI_TRAVELS:
            //nada
            break;
        case URI_TRAVEL_ITEM:
            String id = uri.getPathSegments().get(1);
            count = database.update(TravelsDatabaseHelper.TABLE_NAME, values, Travels._ID +
                    " = " + id + (!TextUtils.isEmpty(selection) ? " AND (" +
                            selection + ')' : ""), selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return count;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    int count = 0;
    database = mDbHelper.getWritableDatabase();
    int match = mUriMatcher.match(uri);

    switch (match){
        case URI_TRAVELS:
            //nada
            break;
        case URI_TRAVEL_ITEM:
            String id = uri.getPathSegments().get(1);
            count = database.delete(TravelsDatabaseHelper.TABLE_NAME, Travels._ID +  " = " + id +
                    (!TextUtils.isEmpty(selection) ? " AND (" +
                            selection + ')' : ""), selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return count;
}

Main Activity - Update and delete methods

public void updateTravel(String city, String country, int year, String note, String id){
    ContentValues updateValues = new ContentValues();
    updateValues.put(Travels.CITY, city);
    updateValues.put(Travels.COUNTRY, country);
    updateValues.put(Travels.YEAR, year);
    updateValues.put(Travels.NOTE, note);

    getContentResolver().update(TravelsProvider.CONTENT_URI, updateValues, Travels._ID+"="+id, null);
}


private void deleteTravel(String id){
    /**Accede a la funcion delete() del Content Provider*/
    getContentResolver().delete(TravelsProvider.CONTENT_URI, Travels._ID+"="+id, null);
}

Main Activity - Context menus where I call delete and update methods

public boolean onContextItemSelected(MenuItem item) {

    AdapterContextMenuInfo menu_info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
    int itemPos = menu_info.position;
    Cursor cursor = mAdapter.getCursor();
    cursor.moveToPosition(itemPos);

    switch (item.getItemId()) {
        case R.id.edit_travel:
            Intent intent = new Intent(this, EditTravelActivity.class);
            intent.putExtra(TravelActivity.EXTRA_ID, cursor.getString(cursor.getColumnIndex(Travels._ID)));
            startActivityForResult(intent, REQUEST_CODE_UPDATE_TRAVEL);
            return true;
        case R.id.delete_travel:
            String ids = cursor.getString(cursor.getColumnIndex(Travels._ID));
            deleteTravel(ids);
            return true;
        default:
            return super.onContextItemSelected(item);
    }
}

protected void onActivityResult (int requestCode, int resultCode, Intent data) {
    //...
            case REQUEST_CODE_UPDATE_TRAVEL:
                String ucity = data.getExtras().getString(TravelActivity.EXTRA_CITY);
                String ucountry = data.getExtras().getString(TravelActivity.EXTRA_COUNTRY);
                int uyear = data.getExtras().getInt(TravelActivity.EXTRA_YEAR);
                String unote = data.getExtras().getString(TravelActivity.EXTRA_NOTE);
                String uid = data.getExtras().getString(TravelActivity.EXTRA_ID);

                updateTravel(ucity, ucountry, uyear, unote, uid);
                break;
        }
    }
}

UPDATE - According to NigelK's answer

This is my "UriMatcher" and other relevant definitions to take care:

private static final String AUTHORITY = "com.example.travellist";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/travels");

private static final int URI_TRAVELS = 1;
private static final int URI_TRAVEL_ITEM = 2;

private static final UriMatcher mUriMatcher;
static {
    mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    mUriMatcher.addURI(AUTHORITY, "travels", URI_TRAVELS);
    mUriMatcher.addURI(AUTHORITY, "travels/#", URI_TRAVEL_ITEM);
}

I don't need to do it in booth ways, I'm learning about this so just doing it in the best way is enough for me to learn.

According to your answer, I've tryed doing it the 2 ways:

private void deleteTravel(long id){
    /*METHOD 1*/
    getContentResolver().delete(TravelsProvider.CONTENT_URI, Travels._ID+"="+id, null);
    /*METHOD 2*/
    Uri uri = ContentUris.withAppendedId(TravelsProvider.CONTENT_URI, id);
    getContentResolver().delete(uri, null, null);
}

public int delete(Uri uri, String selection, String[] selectionArgs) {

    //...

    switch (match){
        case URI_TRAVELS:
            //nada
            break;
        case URI_TRAVEL_ITEM:
            /*METHOD 1*/
            count = database.delete(TravelsDatabaseHelper.TABLE_NAME, selection, selectionArgs);
            break;
            /*METHOD 2*/
            String rowId = uri.getPathSegments().get(1);
            selection = Travels._ID +" = "+ rowId + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
            count = database.delete(TravelsDatabaseHelper.TABLE_NAME, selection, selectionArgs);
            break;
        //...
}

In the first way, it continues whithout deleting the item. In the second way, in the line of String rowId = uri.getPathSegments().get(1); throws me this error: Unreachable code!


Solution

  • Most Content Providers include a shortcut URI pattern that allows you to address a particular row by appending a row ID to the content URI. Where you do the following, you are providing the selection "WHERE _id = id", which is perfectly valid:

    .delete(TravelsProvider.CONTENT_URI, Travels._ID+"="+id, null);
    

    The uri above will be something like: content://com.package.provider/content

    The short cut, which applies the action to just the id appended to the uri, is of the form:

    uri = ContentUris.withAppendedId(TravelsProvider.CONTENT_URI, id);
    .delete(uri, null, null);
    

    The uri above will now be something like: content://com.package.provider/content/1234

    The problem you have is that in your delete() and update() methods, you are trying to deal with the 2nd form, not the 1st (which is the URI form you're using in the call). Expand your uriMatcher to tell the difference, something like:

    uriMatcher.addURI("your.package.provider", "content", URI_TRAVEL_ITEM);
    uriMatcher.addURI("your.package.provider", "content/#", URI_TRAVEL_ITEM_ID);
    

    In delete(), and similarly for update():

    switch (match){
        case URI_TRAVELS:
            //nada
            break;
    
        case URI_TRAVEL_ITEM:
            count = database.delete(TravelsDatabaseHelper.TABLE_NAME, selection, selectionArgs);
            break;
    
        case URI_TRAVEL_ITEM_ID:
            String rowID = uri.getPathSegments().get(1);
            selection = Travels._ID + "=" + rowID
         + (!TextUtils.isEmpty(selection) ? 
            " AND (" + selection + ')' : "");
            count = database.delete(TravelsDatabaseHelper.TABLE_NAME, selection, selectionArgs);
            break;
    
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    

    So where there is no id appended to the URI (it matches URI_TRAVEL_ITEM) it is a straightforward delete based upon the selection and arguments. Where there is an id appended to the URI (it matches URI_TRAVEL_ITEM_ID), get the id from the URI using getPathSegments().get(1) and use the value to augment the selection passed in (so it applies to just that row) before doing the delete.

    You've then got both types of URI covered and the rest of your code should (I hope) work.