When I try to update my data I get this error
java.lang.IllegalArgumentException: Update is not supported for content://com.example.recodedharran.booksinventory/books/3
I used the debugger and the match value appears to be -1 !
public int update(Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
switch (match) {
case BOOKS:
return updateBook(uri, contentValues, selection, selectionArgs);
case BOOKS_ID:
// For the PET_ID code, extract out the ID from the URI,
// so we know which row to update. Selection will be "_id=?" and selection
// arguments will be a String array containing the actual ID.
selection = BooksContract.BooksEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
return updateBook(uri, contentValues, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
}
}
private int updateBook(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if (values.containsKey(BooksContract.BooksEntry.COLUMN_PRODUCT_NAME)) {
String name = values.getAsString(BooksContract.BooksEntry.COLUMN_PRODUCT_NAME);
if (name == null) {
throw new IllegalArgumentException("Pet requires a name");
}
if (values.size() == 0) {
return 0;
}
}
SQLiteDatabase database = mDbHelper.getReadableDatabase();
int rowsUpdated = database.update(BooksContract.BooksEntry.TABLE_NAME, values, selection, selectionArgs);
// If 1 or more rows were updated, then notify all listeners that the data at the
// given URI has changed
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
// Return the number of rows updated
return rowsUpdated;
}
you are throwing this Exception here.
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
It's clear that match does not equal the value in both cases (BOOKS_ID, BOOK)
check that (BOOKS_ID, BOOKS) always satisfy the switch statement or remove or change the
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
to
default: return -1; //return `-1` for example or any integer that gives a relevant indication.
Here, this was the problem :]
sUriMatcher.addURI(BooksContract.CONTENT_AUTHORITY, BooksContract.PATH_BOOKS + "#/", BOOK_ID);
Related
I have different apps that share some data with each others, that's done through Content Provider but when I uploaded the apk I received an email saying "Your app(s) are using a content provider that contains a SQL Injection vulnerability."
There are a couple of ways for fixing this according to Google guide:
If an affected ContentProvider needs to be exposed to other apps:
You can prevent SQL Injection into SQLiteDatabase.query by using
strict mode with a projection map. Strict mode protects against
malicious selection clauses and projection map protects against
malicious projection clauses. You must use both of these features to ensure that your queries are safe.
You can prevent SQL Injection into SQLiteDatabase.update and SQLiteDatabase.delete by using a selection clause that uses '?' as a replaceable parameter and a separate array of selection arguments. Your selection clause should not be constructed from untrusted inputs.
But is not clear to me how to proceed with any of the solutions, I don't get exactly how to use the projection map or change the code using selection clause that uses '?'. I mean, I have seen some examples about ProjectionMap but what are the key/value it needs for the query? Do I need to write explicit the values I want? but what if it is a generic method and I don't know in that part of the code what do I want to get? Or how do I convert any query to a ProjectionMap?
I hope I'm explaining myself on this.
Here is my code:
#Override
public boolean onCreate() {
gOpenHelper = new GameDBHelper(getContext());
return true;
}
/**
* Builds a UriMatcher that is used to determine witch database request is being made.
*/
public static UriMatcher buildUriMatcher(){
String content = GamesContract.CONTENT_AUTHORITY;
// All paths to the UriMatcher have a corresponding code to return
// when a match is found (the ints above).
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(content, GamesContract.PATH_GAME, GAME);
matcher.addURI(content, GamesContract.PATH_GAME + "/#", GAME_ID);
return matcher;
}
#Override
public String getType(Uri uri) {
switch(sUriMatcher.match(uri))
{
case GAME:
return GameEntry.CONTENT_TYPE;
case GAME_ID:
return GameEntry.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase db = gOpenHelper.getWritableDatabase();
Cursor retCursor;
switch(sUriMatcher.match(uri))
{
case GAME:
retCursor = db.query(
GameEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case GAME_ID:
long _id = ContentUris.parseId(uri);
retCursor = db.query(
GameEntry.TABLE_NAME,
projection,
GameEntry._ID + " = ?",
new String[]{String.valueOf(_id)},
null,
null,
sortOrder
);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = gOpenHelper.getWritableDatabase();
long _id;
Uri returnUri;
switch(sUriMatcher.match(uri))
{
case GAME:
_id = db.insert(GameEntry.TABLE_NAME, null, values);
if(_id > 0){
returnUri = GameEntry.BuildGameUri(_id);
} else{
throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
}
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = gOpenHelper.getWritableDatabase();
int rows; // Number of rows effected
switch(sUriMatcher.match(uri))
{
case GAME:
rows = db.delete(GameEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Because null could delete all rows:
if(selection == null || rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = gOpenHelper.getWritableDatabase();
int rows;
switch(sUriMatcher.match(uri))
{
case GAME:
rows = db.update(GameEntry.TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if(rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
}
To anyone who can be looking for the answer, it was pretty simpe, I think it was getting hard for me because I wasn't familiar with many concepts about sql on Android.
The problem goes with the queries that require a 'where' clause; in my case update and delete, the things I needed to do in order to fix it was to not use the selection parameter as it is, I needed to do something like:
GamesContract.Games.GAME_NAME + " = ?"
db.delete(GamesContract.Games.TABLE_NAME, GamesContract.Games.GAME_NAME + " = ?", selectionArgs);
Basicaly the table name + " = ?" selection.
This way you are forcing to update only the needed table and prevent the sql injection to modify any place in your database through that same method.
I fixed it using SQLiteQueryBuilder
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
final SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(TABLE_NAME);
builder.setProjectionMap(buildProjectionMap());
builder.setStrict(true);
count = builder.delete(db, selection, selectionArgs);
}else {
count = db.delete(TABLE_NAME, selection, selectionArgs);
}
I am struggling to get the update method working in my content provider, the update returns 0 and there is no updated information in the table. The table is populated at this point.
Here's the update function:
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = leagueDBHelper.getWritableDatabase();
int rowsUpdated = 0;
int uriType = uriMatcher.match(uri);
switch (uriType) {
case LEAGUES:
rowsUpdated = db.update(LeagueContract.LEAGUE_TABLE_NAME,
values, selection, selectionArgs);
break;
case LEAGUE_ID:
String newSelection = appendToSelection(uri, selection);
rowsUpdated = db.update(LeagueContract.LEAGUE_TABLE_NAME,
values, newSelection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unrecognised uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri,null);
return rowsUpdated;
}
private String appendToSelection(Uri uri, String selection) {
String id = uri.getLastPathSegment();
StringBuilder newSelection = new StringBuilder(LeagueContract.COLUMN_KEY_ID + "=" + id);
if (selection != null && !selection.isEmpty()) {
newSelection.append(" AND " + selection);
}
return newSelection.toString();
}
Here's where I call the update of the content provider:
message = "Player 1 wins";// we should get the name
ContentValues mUpdateValues = new ContentValues();
String[] projectionFields = new String[] {
LeagueContract.COLUMN_NAME_SCORE };
Uri uri = ContentUris.withAppendedId(LeagueContentProvider.LEAGUE_URI, player1Id);
ContentResolver content = getContentResolver();
Cursor cursor = content.query(uri, projectionFields, null, null, null);
cursor.moveToFirst();
int score = cursor.getInt(0);
score ++;
ContentValues values = new ContentValues();
values.put(LeagueContract.COLUMN_NAME_SCORE,score);
content.update(uri,values,null,null);
Connect to your DB with the remote shell and try to update manually.
Also you can add log messages in your content provider to trace values.
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!
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.
I have looked at this for a couple of days now and I completely can't work out why my content provider return 0 using the arguments I am passing it.
Here's my contentResolver code:
String[] expenditureProjection = {
BusinessOpsDatabase.COL_EXPEND_CAT_ID,
BusinessOpsDatabase.COL_EXPEND_DATE,
BusinessOpsDatabase.COL_EXPEND_AMOUNT,
BusinessOpsDatabase.COL_EXPEND_DESC,
BusinessOpsDatabase.COL_STERLING_EXCHANGE,
BusinessOpsDatabase.COL_COMPANY_ID,
BusinessOpsDatabase.CURRENCY_ID,
BusinessOpsDatabase.COL_MOD_DATE
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = {expend_id.trim()};
selectionClause = BusinessOpsExpenditureProvider.EXPENDITURE_ID + "=?";
Log.d(TAG, expend_id+" Selected from list.");
Cursor expendCursor = getContentResolver().query(
BusinessOpsExpenditureProvider.CONTENT_URI, expenditureProjection, selectionClause, selectionArgs, null);
if (null == expendCursor) {
Log.d(TAG, "Expenditure cursor: Is null");
} else if (expendCursor.getCount() < 1) {
Log.d(TAG,"Expenditure cursor: Search was unsuccessful: "+expendCursor.getCount());
} else {
Log.d(TAG,"Expenditure cursor: Contains results");
int i=0;
expendCursor.moveToFirst();
// loop through cursor and populate country array
while (expendCursor.isAfterLast() == false)
{
expend_date_edit.setText(expendCursor.getString(1));
expend_amount_edit.setText(expendCursor.getString(3));
expend_desc_edit.setText(expendCursor.getString(4));
i++;
expendCursor.moveToNext();
}
}
Here's my content provider query method:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDB.getWritableDatabase();
// A convenience class to help build the query
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(BusinessOpsDatabase.TABLE_EXPENDITURE);
switch (sURIMatcher.match(uri)) {
case EXPENDITURE:
if(selection != null && selectionArgs != null){
//values.get("company_contact");
String segment = uri.getLastPathSegment();
Log.d(TAG, "Last path segment: "+ segment);
String whereClause = BusinessOpsDatabase.EXPENDITURE_ID + "="+ selectionArgs[0];
Log.d(TAG, "Where clause: "+whereClause);
}
break;
case EXPENDITURE_ID:
// If this is a request for an individual status, limit the result set to that ID
qb.appendWhere(BusinessOpsDatabase.EXPENDITURE_ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
// Query the underlying database
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null);
// Notify the context's ContentResolver if the cursor result set changes
c.setNotificationUri(getContext().getContentResolver(), uri);
// Return the cursor to the result set
return c;
}
I'm printing the whereclause to the log and I see '_id=3' which should be fine because I have pulled off a copy of my SQLite database and I can see that the expenditure table has an _id 3 row in it. Any Ideas?
What an epic problem this has been. I found the error in my ContentResolver code.
selectionClause = BusinessOpsExpenditureProvider.EXPENDITURE_ID + "=?";
I was using the EXPENDITURE_ID variable from the provider rather than the database class. The line now reads.
selectionClause = BusinessOpsDatabase.EXPENDITURE_ID + "=?";
And works!
Strange behaviour of SQLite update in ContentProvider.
Update method:
#Override
public int update(Uri uri, ContentValues updateValues, String whereClause, String[] whereValues) {
SQLiteDatabase db = TasksContentProvider.dbHelper.getWritableDatabase();
int updatedRowsCount;
String finalWhere;
db.beginTransaction();
// Perform the update based on the incoming URI's pattern
try {
switch (uriMatcher.match(uri)) {
case MATCHER_TASKS:
updatedRowsCount = db.update(TasksTable.TABLE_NAME, updateValues, whereClause, whereValues);
break;
case MATCHER_TASK:
String id = uri.getPathSegments().get(TasksTable.TASK_ID_PATH_POSITION);
finalWhere = TasksTable._ID + " = " + id;
// if we were passed a 'where' arg, add that to our 'finalWhere'
if (whereClause != null) {
finalWhere = finalWhere + " AND " + whereClause;
}
updatedRowsCount = db.update(TasksTable.TABLE_NAME, updateValues, finalWhere, whereValues);
break;
default:
// Incoming URI pattern is invalid: halt & catch fire.
throw new IllegalArgumentException("Unknown URI " + uri);
}
} finally {
db.endTransaction();
}
if (updatedRowsCount > 0) {
DVSApplication.getContext().getContentResolver().notifyChange(uri, null);
}
return updatedRowsCount;
}
Query method:
#Override
public Cursor query(Uri uri, String[] selectedColumns, String whereClause, String[] whereValues, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
// Choose the projection and adjust the "where" clause based on URI pattern-matching.
switch (uriMatcher.match(uri)) {
case MATCHER_TASKS:
qb.setTables(TasksTable.TABLE_NAME);
qb.setProjectionMap(tasksProjection);
break;
// asking for a single comic - use the rage comics projection, but add a where clause to only return the one
// comic
case MATCHER_TASK:
qb.setTables(TasksTable.TABLE_NAME);
qb.setProjectionMap(tasksProjection);
// Find the comic ID itself in the incoming URI
String taskId = uri.getPathSegments().get(TasksTable.TASK_ID_PATH_POSITION);
qb.appendWhere(TasksTable._ID + "=" + taskId);
break;
case MATCHER_TASK_COMMENTS:
qb.setTables(TaskCommentsTable.TABLE_NAME);
qb.setProjectionMap(taskCommentsProjection);
break;
case MATCHER_TASK_COMMENT:
qb.setTables(TaskCommentsTable.TABLE_NAME);
qb.setProjectionMap(taskCommentsProjection);
String commentId = uri.getPathSegments().get(TaskCommentsTable.TASK_COMMENT_ID_PATH_POSITION);
qb.appendWhere(TaskCommentsTable._ID + "=" + commentId);
break;
default:
// If the URI doesn't match any of the known patterns, throw an exception.
throw new IllegalArgumentException("Unknown URI " + uri);
}
SQLiteDatabase db = TasksContentProvider.dbHelper.getReadableDatabase();
// the two nulls here are 'grouping' and 'filtering by group'
Cursor cursor = qb.query(db, selectedColumns, whereClause, whereValues, null, null, sortOrder);
// Tell the Cursor about the URI to watch, so it knows when its source data changes
cursor.setNotificationUri(DVSApplication.getContext().getContentResolver(), uri);
return cursor;
}
Trying to update and row.
int affectedRowsCount = provider.update(Uri.parse(TasksTable.CONTENT_URI.toString() + "/"+ taskId), task.getContentValues(), null, null);
affectedRowsCount is eqaul to 1
Check if row is updated
Cursor cs = provider.query(TasksTable.CONTENT_URI, new String[] {TasksTable.TASK_STATE_VALUE}, TasksTable._ID +" = ?", new String[] {String.valueOf(taskId)}, null);
if(cs.moveToFirst()) {
String state = cs.getString(cs.getColumnIndex(TasksTable.TASK_STATE_VALUE));
}
state is the same as before update. Though update went succesful because affectedRowsCount is equal to 1 but selecting by the same id the same row seems that row wasn't updated at all.
In your update method you are using a transaction, but you never set the result as successful, so everytime you reach db.endTransaction() a rollback is performed. That's why your update isn't stored.
The changes will be rolled back if any transaction is ended without
being marked as clean (by calling setTransactionSuccessful). Otherwise
they will be committed.
You need to use
db.setTransactionSuccessful();
when your update is finished without errors. In your code, it should be after both your db.update.