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.
Related
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);
I have the problem, that my ListView shows the data only one time. If am closing the application and open it again, it doesn't show anything.
I have a Content Provider for retrieving the data from a SQLite database. I have a SyncAdapter which is getting the data from a Server and puts it into the SQLite database. Then I use a SimpleCursorAdapter for showing the data into a ListView. The query is an 'inner join'.
MainActivity Code:
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private static final int URL_LOADER = 1;
private ListView employeeList;
public static SimpleCursorAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
employeeList = (ListView) findViewById(R.id.listView);
/**
* Creating a dummyAccount for the content provider
*/
Account account = new Account("dummyAccount", TrackingContentProvider.AUTHORITY);
AccountManager accountManager = (AccountManager) this.getSystemService(ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(account, null, null);
/**
* Set the sync adapter to an automatic state
*/
ContentResolver.setSyncAutomatically(account, TrackingContentProvider.AUTHORITY, true);
/**
* The projection for the SimpleCursorLoader
*/
String[] from = new String[] {EmployeesTable.COLUMN_FIRST_NAME, EmployeesTable.COLUMN_LAST_NAME, StatesTable.COLUMN_STATE, EmployeesTable.COLUMN_ID};
/**
* The ids of the views where the content is going to be put in
*/
int[] to = new int[] {R.id.tV_emplFirstNameInsert, R.id.tV_emplLastNameInsert, R.id.tV_stateInsert};
/**
* Make a new cursor adapter for the list view
*/
adapter = new SimpleCursorAdapter(this, R.layout.employee_list_item, null, from, to, 0);
/**
* Calls the loader manager and query another time
*/
getSupportLoaderManager().restartLoader(URL_LOADER, null, MainActivity.this);
/**
* Sets the list view to the SimpleCursorAdapter
*/
employeeList.setAdapter(adapter);
registerClickCallback();
}
/**
* Registers the clicks on the elements
*/
private void registerClickCallback() {
employeeList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
startActivity(new Intent(MainActivity.this, LocationActivity.class));
}
});
}
/**
* Is called by the loader manager
* #param id The unique id of the loader
* #param args Bundle of arguments
* #return The cursor of the query
*/
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = new String[] {"E." + EmployeesTable.COLUMN_ID, "E." + EmployeesTable.COLUMN_FIRST_NAME,
"E." + EmployeesTable.COLUMN_LAST_NAME, "S." + StatesTable.COLUMN_STATE};
String clause = "S." + StatesTable.COLUMN_ID + " = " + "E." + EmployeesTable.COLUMN_STATE_ID;
return new CursorLoader(this,
TrackingContentProvider.EMPLOYEE_LIST_CONTENT_URI, projection, clause, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
#Override
protected void onRestart() {
super.onRestart();
getSupportLoaderManager().restartLoader(URL_LOADER, null, MainActivity.this);
}
}
The Content Provider queries the data with a SQLiteQueryBuilder. The table I used for the query looks like this:
private static final String EMPLOYEE_LIST_INNER_JOIN = "" +
EmployeesTable.TABLE_NAME + " AS E " +
"INNER JOIN " +
StatesTable.TABLE_NAME + " AS S";
The onPerformSync looks like this:
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
try {
deleteDatabase(provider);
insertEmployeesTable(provider);
insertStatesTable(provider);
insertLocationsTable(provider);
} catch (RemoteException | IOException e) {
Log.e("Sync: Error:", e.toString());
}
}
So every sync, it deletes all entries of the database and inserts the data new. It works, I checked the database after sync.
After the application is started for the first time, it doesn't shows anything, since the sync adapter is syncing. Then I close the app and opens it again and it shows the data. Then the sync adapter syncs another time (same data) but it doesn't shows the data anymore. Any suggestions? I am now trying to find the error for 3 days...
EDIT 1: ContentProvider Code:
public class TrackingContentProvider extends ContentProvider {
private static final int TRACKING_CONTENT = 100;
private static final int TRACKING_CONTENT_ID = 200;
private static final int EMPLOYEE_LIST_CONTENT = 300;
private static final int EMPLOYEE_LIST_CONTENT_ID = 400;
/**
* Table declaration
*/
private static final int EMPLOYEES_TABLE_CONTENT = 500;
private static final int EMPLOYEES_TABLE_CONTENT_ID = 600;
private static final int STATES_TABLE_CONTENT = 700;
private static final int STATES_TABLE_CONTENT_ID = 800;
private static final int LOCATIONS_TABLE_CONTENT = 900;
private static final int LOCATIONS_TABLE_CONTENT_ID = 1000;
public static final String AUTHORITY = "***";
private static final String TRACKING_BASE_PATH = "tracking";
private static final String EMPLOYEE_LIST_BASE_PATH = "employees";
/**
* Table base paths
*/
private static final String EMPLOYEES_TABLE_BASE_PATH = "employeesTable";
private static final String STATES_TABLE_BASE_PATH = "statesTable";
private static final String LOCATIONS_TABLE_BASE_PATH = "locationsTable";
/**
* Table INNER JOIN declarations
*/
private static final String EMPLOYEE_LIST_INNER_JOIN = "" +
EmployeesTable.TABLE_NAME + " AS E " +
"INNER JOIN " +
StatesTable.TABLE_NAME + " AS S";
public static final Uri TRACKING_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TRACKING_BASE_PATH);
public static final Uri EMPLOYEE_LIST_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + EMPLOYEE_LIST_BASE_PATH);
/**
* Tables Uris
*/
public static final Uri EMPLOYEES_TABLE_URI = Uri.parse("content://" + AUTHORITY + "/" + EMPLOYEES_TABLE_BASE_PATH);
public static final Uri STATES_TABLE_URI = Uri.parse("content://" + AUTHORITY + "/" + STATES_TABLE_BASE_PATH);
public static final Uri LOCATIONS_TABLE_URI = Uri.parse("content://" + AUTHORITY + "/" + LOCATIONS_TABLE_BASE_PATH);
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, TRACKING_BASE_PATH, TRACKING_CONTENT);
sUriMatcher.addURI(AUTHORITY, TRACKING_BASE_PATH + "/#", TRACKING_CONTENT_ID);
sUriMatcher.addURI(AUTHORITY, EMPLOYEE_LIST_BASE_PATH, EMPLOYEE_LIST_CONTENT);
sUriMatcher.addURI(AUTHORITY, EMPLOYEE_LIST_BASE_PATH + "/#", EMPLOYEE_LIST_CONTENT_ID);
/**
* Table matcher
*/
sUriMatcher.addURI(AUTHORITY, EMPLOYEES_TABLE_BASE_PATH, EMPLOYEES_TABLE_CONTENT);
sUriMatcher.addURI(AUTHORITY, EMPLOYEES_TABLE_BASE_PATH + "/#", EMPLOYEES_TABLE_CONTENT_ID);
sUriMatcher.addURI(AUTHORITY, STATES_TABLE_BASE_PATH, STATES_TABLE_CONTENT);
sUriMatcher.addURI(AUTHORITY, STATES_TABLE_BASE_PATH + "/#", STATES_TABLE_CONTENT_ID);
sUriMatcher.addURI(AUTHORITY, LOCATIONS_TABLE_BASE_PATH, LOCATIONS_TABLE_CONTENT);
sUriMatcher.addURI(AUTHORITY, LOCATIONS_TABLE_BASE_PATH + "/#", LOCATIONS_TABLE_CONTENT_ID);
}
private SQLiteHelper database;
#Override
public boolean onCreate() {
database = new SQLiteHelper(getContext());
return false;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriType = sUriMatcher.match(uri);
checkColumns(projection);
switch (uriType) {
case TRACKING_CONTENT:
break;
case TRACKING_CONTENT_ID:
break;
case EMPLOYEE_LIST_CONTENT:
queryBuilder.setTables(EMPLOYEE_LIST_INNER_JOIN);
break;
case EMPLOYEE_LIST_CONTENT_ID:
queryBuilder.setTables(EMPLOYEE_LIST_INNER_JOIN);
queryBuilder.appendWhere("E." + EmployeesTable.COLUMN_ID + " = " + uri.getLastPathSegment());
break;
case STATES_TABLE_CONTENT:
queryBuilder.setTables(StatesTable.TABLE_NAME);
break;
case STATES_TABLE_CONTENT_ID:
queryBuilder.setTables(StatesTable.TABLE_NAME);
queryBuilder.appendWhere(StatesTable.COLUMN_ID + " = " + uri.getLastPathSegment());
break;
case EMPLOYEES_TABLE_CONTENT:
queryBuilder.setTables(EmployeesTable.TABLE_NAME);
break;
case EMPLOYEES_TABLE_CONTENT_ID:
queryBuilder.setTables(EmployeesTable.TABLE_NAME);
queryBuilder.appendWhere(EmployeesTable.COLUMN_ID + " = " + uri.getLastPathSegment());
break;
case LOCATIONS_TABLE_CONTENT:
queryBuilder.setTables(LocationsTable.TABLE_NAME);
break;
case LOCATIONS_TABLE_CONTENT_ID:
queryBuilder.setTables(LocationsTable.TABLE_NAME);
queryBuilder.appendWhere(LocationsTable.COLUMN_ID + " = " + uri.getLastPathSegment());
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = database.getReadableDatabase();
Cursor cursor = queryBuilder.query(
db,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
long id;
String path;
switch (uriType) {
case STATES_TABLE_CONTENT:
id = db.insert(StatesTable.TABLE_NAME, null, values);
path = STATES_TABLE_BASE_PATH;
break;
case LOCATIONS_TABLE_CONTENT:
id = db.insert(LocationsTable.TABLE_NAME, null, values);
path = LOCATIONS_TABLE_BASE_PATH;
break;
case EMPLOYEES_TABLE_CONTENT:
id = db.insert(EmployeesTable.TABLE_NAME, null, values);
path = EMPLOYEES_TABLE_BASE_PATH;
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(path + "/" + id);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case EMPLOYEES_TABLE_CONTENT:
rowsDeleted = db.delete(EmployeesTable.TABLE_NAME,
selection,
selectionArgs);
break;
case EMPLOYEES_TABLE_CONTENT_ID:
String employeeID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = db.delete(EmployeesTable.TABLE_NAME,
EmployeesTable.COLUMN_ID + " = " + employeeID, null);
}
break;
case STATES_TABLE_CONTENT:
rowsDeleted = db.delete(StatesTable.TABLE_NAME,
selection,
selectionArgs);
break;
case STATES_TABLE_CONTENT_ID:
String stateID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = db.delete(StatesTable.TABLE_NAME,
StatesTable.COLUMN_ID + " = " + stateID, null);
}
break;
case LOCATIONS_TABLE_CONTENT:
rowsDeleted = db.delete(StatesTable.TABLE_NAME,
selection,
selectionArgs);
break;
case LOCATIONS_TABLE_CONTENT_ID:
String locationID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = db.delete(LocationsTable.TABLE_NAME,
LocationsTable.COLUMN_ID + " = " + locationID, null);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase sqlDb = database.getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case EMPLOYEES_TABLE_CONTENT:
rowsUpdated = sqlDb.update(EmployeesTable.TABLE_NAME,
values,
selection,
selectionArgs);
break;
case EMPLOYEE_LIST_CONTENT_ID:
String employeeID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDb.update(EmployeesTable.TABLE_NAME,
values,
EmployeesTable.COLUMN_ID + "=" + employeeID
+ " AND "
+ selection,
selectionArgs);
} else {
rowsUpdated = sqlDb.update(EmployeesTable.TABLE_NAME,
values,
EmployeesTable.COLUMN_ID + "=" + employeeID
+ " AND "
+ selection,
selectionArgs);
}
break;
case STATES_TABLE_CONTENT:
rowsUpdated = sqlDb.update(StatesTable.TABLE_NAME,
values,
selection,
selectionArgs);
break;
case STATES_TABLE_CONTENT_ID:
String stateID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDb.update(StatesTable.TABLE_NAME,
values,
StatesTable.COLUMN_ID + "=" + stateID
+ " AND "
+ selection,
selectionArgs);
} else {
rowsUpdated = sqlDb.update(StatesTable.TABLE_NAME,
values,
StatesTable.COLUMN_ID + "=" + stateID
+ " AND "
+ selection,
selectionArgs);
}
break;
case LOCATIONS_TABLE_CONTENT:
rowsUpdated = sqlDb.update(LocationsTable.TABLE_NAME,
values,
selection,
selectionArgs);
break;
case LOCATIONS_TABLE_CONTENT_ID:
String locationID = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDb.update(LocationsTable.TABLE_NAME,
values,
LocationsTable.COLUMN_ID + "=" + locationID
+ " AND "
+ selection,
selectionArgs);
} else {
rowsUpdated = sqlDb.update(LocationsTable.TABLE_NAME,
values,
LocationsTable.COLUMN_ID + "=" + locationID
+ " AND "
+ selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Checks if the column exists
* #param projection The array of the existing columns
*/
private void checkColumns(String[] projection) {
String[] available = {
"E." + EmployeesTable.COLUMN_ID,
"E." + EmployeesTable.COLUMN_LAST_NAME,
"E." + EmployeesTable.COLUMN_FIRST_NAME,
"E." + EmployeesTable.COLUMN_STATE_ID,
"E." + EmployeesTable.COLUMN_ENTRY_DATE,
"S." + StatesTable.COLUMN_ID,
"S." + StatesTable.COLUMN_STATE,
"L." + LocationsTable.COLUMN_ID,
"L." + LocationsTable.COLUMN_DATE,
"L." + LocationsTable.COLUMN_EMPLOYEE_ID,
"L." + LocationsTable.COLUMN_LONGITUDE,
"L." + LocationsTable.COLUMN_LATITUDE,
EmployeesTable.COLUMN_ID,
EmployeesTable.COLUMN_LAST_NAME,
EmployeesTable.COLUMN_FIRST_NAME,
EmployeesTable.COLUMN_STATE_ID,
EmployeesTable.COLUMN_ENTRY_DATE,
StatesTable.COLUMN_ID,
StatesTable.COLUMN_STATE,
LocationsTable.COLUMN_ID,
LocationsTable.COLUMN_DATE,
LocationsTable.COLUMN_EMPLOYEE_ID,
LocationsTable.COLUMN_LONGITUDE,
LocationsTable.COLUMN_LATITUDE,
};
if (projection != null) {
HashSet<String> requestedColumns = new HashSet<>(Arrays.asList(projection));
HashSet<String> availableColumns = new HashSet<>(Arrays.asList(available));
if (!availableColumns.containsAll(requestedColumns)) {
throw new IllegalArgumentException("Unknown columns in projection");
}
}
}
}
UPDATE:
I finally found the mistake. During the synchronization, there is a mistake with the auto increment. So, he won't find a entry with this Inner Join statement. finished.
So I'm getting an unreachable statement in the following switch statement. I can't figure out why this is happening, any help would be much appreciated.
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = psHelper.getWritableDatabase();
int delCount = 0;
return 0;
switch (URI_MATCHER.match(uri)) {
case PRODUCT_LIST:
// THE LINE BELOW IS UNREACHABLE
delCount = db.delete(TABLE_NAME_PRODUCTS, selection, selectionArgs);
break;
case PRODUCT_ID:
String idStr = uri.getLastPathSegment();
String where = PSContract.Products.ID_COLUMN + " = " + idStr;
if (!TextUtils.isEmpty(selection)) {
where += " AND " + selection;
}
delCount = db.delete(TABLE_NAME_PRODUCTS, where, selectionArgs);
break;
case SUPPLIER_LIST:
delCount = db.delete(TABLE_NAME_SUPPLIERS, selection, selectionArgs);
break;
case SUPPLIER_ID:
String idStr2 = uri.getLastPathSegment();
String where2 = PSContract.Products.ID_COLUMN + " = " + idStr2;
if (!TextUtils.isEmpty(selection)) {
where2 += " AND " + selection;
}
delCount = db.delete(TABLE_NAME_SUPPLIERS, where2, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return delCount;
}
You're returning 0 right before the switch. There is no way that the switch can be reached if you return from the method right before, so the error is generated.
Under int delCount = 0 you are returning 0 which is going end the function call. Since that return is in a place where it will happen 100% of the time nothing below it will ever execute. Looks like a typo to me.
I've got two loaders, each of which loads data from a different content provider.
The fragment is supplied with the ID of a medication from the first content provider, and the first loader loads all of the information related to that medication. The second loader is supposed to query the second content provider for all of the alarms associated with that medication.
The first loader works just fine, and returns all of the correct data. However, the second loader appears to return a null cursor, even though I know for a fact that there is plenty of data in the table that should be relevant. I say "appears" because using getCount() on the data in in onLoadFinished for the second loader causes my app to crash, and the only reason I can think that this would occur is if the cursor were null.
Anyway, here's the code for my loaders. If you need, I can give you the code for anything else you want.
/**
* Initializes the loaders.
*/
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
CursorLoader loader = null;
long id = getArguments().getLong(ARG_MED_ID);
switch(loaderId) {
case 0: // MedList Loader
Log.d("MedManager", "Loading med data");
Uri singleUri = ContentUris.withAppendedId(MedProvider.CONTENT_URI, id);
String[] projection = { MedTable.MED_ID,
MedTable.MED_NAME,
MedTable.MED_DOSAGE,
MedTable.MED_DATE_FILLED,
MedTable.MED_DURATION };
loader = new CursorLoader(getActivity(), singleUri,
projection, null, null,
MedTable.MED_NAME + " COLLATE LOCALIZED ASC");
break;
case 1: // AlarmList Loader
Log.d("MedManager", "Theoretically loading alarm list");
Uri baseUri = AlarmProvider.CONTENT_URI;
// Create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String[] alarmProjection = { DailyAlarmTable.ALARM_ID,
DailyAlarmTable.ALARM_MEDNUM,
DailyAlarmTable.ALARM_TIME };
String select = "((" + DailyAlarmTable.ALARM_MEDNUM + " NOTNULL) AND ("
+ DailyAlarmTable.ALARM_MEDNUM + " = " + id + "))";
loader = new CursorLoader(getActivity(), baseUri,
alarmProjection, select, null,
DailyAlarmTable.ALARM_TIMESTAMP + " ASC");
break;
}
return loader;
}
/**
* Customizes the various TextViews in the layout to match
* the values pulled from the MedTable, or swaps the alarm cursor
* into the adapter.
*/
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case 0:
setUpMedDetails(data);
break;
case 1:
Log.d("MedManager", "Alarm finished loading");
/*
* these lines are commented out because their presence causes
* the app to crash.
*/
/*
boolean isEmpty = data.getCount() < 1;
if(isEmpty) {
Log.d("MedManager", "No results");
}
*/
mAdapter.swapCursor(data);
break;
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
// TODO Auto-generated method stub
if(arg0.getId() == 1) {
mAdapter.swapCursor(null);
}
}
EDIT: For completeness' sake, and because the possibility always exists that I'm simply a massive idiot overlooking something obvious, here's the code by which I add alarms into the table:
/**
* This function will turn the hour and day into an "HH:mm AM/PM" string,
* calculate the timestamp, and then inserts them into the table.
*/
#Override
public void onTimePicked(int hourOfDay, int minute) {
Log.d("MedManager", "onTimePicked triggered");
// Convert the hour and minute into a string
String alarmString = formatAlarmString(hourOfDay, minute);
// Convert the hour and minute into a timestamp
long alarmTimestamp = getAlarmTimestamp(hourOfDay, minute);
// Define the URI to receive the results of the insertion
Uri newUri = null;
// Define a contentValues object to contain the new Values
ContentValues mValues = new ContentValues();
// Add medId;
long medId = getIntent().getLongExtra(MedDetailFragment.ARG_MED_ID, 0);
mValues.put(DailyAlarmTable.ALARM_MEDNUM, medId);
// Add the timestamp
mValues.put(DailyAlarmTable.ALARM_TIMESTAMP, alarmTimestamp);
// Add the time string
mValues.put(DailyAlarmTable.ALARM_TIME, alarmString);
// Insert the new alarm
Toast.makeText(getApplicationContext(), "medNum = " + medId, Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), "time = " + alarmString, Toast.LENGTH_SHORT).show();
newUri = getContentResolver().insert(AlarmProvider.CONTENT_URI, mValues);
String uriStr = newUri.toString();
Toast.makeText(getApplicationContext(), "Uri = " + uriStr, Toast.LENGTH_SHORT).show();
}
As requested, here's my AlarmProvider class.
package com.gmail.jfeingold35.medicationmanager.alarmprovider;
import java.util.Arrays;
import java.util.HashSet;
import com.gmail.jfeingold35.medicationmanager.database.AlarmDatabaseHelper;
import com.gmail.jfeingold35.medicationmanager.database.DailyAlarmTable;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class AlarmProvider extends ContentProvider {
// Database
private AlarmDatabaseHelper database;
// Used for the UriMatcher
private static final int ALARMS = 10;
private static final int ALARM_ID = 20;
private static final String AUTHORITY = "com.gmail.jfeingold35.medicationmanager.alarmprovider";
private static final String BASE_PATH = "medicationmanager";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/alarms";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/alarm";
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, BASE_PATH, ALARMS);
sUriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", ALARM_ID);
}
#Override
public boolean onCreate() {
database = new AlarmDatabaseHelper(getContext());
return false;
}
/**
* Perform a query from the alarm database
*/
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Using SQLiteQueryBuilder instead of the query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Check if the caller requested a column which doesn't exist
checkColumns(projection);
// Set the table
queryBuilder.setTables(DailyAlarmTable.TABLE_ALARM);
int uriType = sUriMatcher.match(uri);
switch(uriType) {
case ALARMS:
break;
case ALARM_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(DailyAlarmTable.ALARM_ID + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = database.getWritableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
// Make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return null;
}
/**
* Delete from the alarm database
*/
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
int rowsDeleted = 0;
switch(uriType) {
case ALARMS:
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM, selection,
selectionArgs);
break;
case ALARM_ID:
String id = uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)) {
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM,
DailyAlarmTable.ALARM_ID + "=" + id, null);
} else {
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM,
DailyAlarmTable.ALARM_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
long id = 0;
switch(uriType) {
case ALARMS:
id = db.insert(DailyAlarmTable.TABLE_ALARM, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
int rowsUpdated = 0;
switch(uriType) {
case ALARMS:
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
selection,
selectionArgs);
break;
case ALARM_ID:
String id = uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)) {
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
DailyAlarmTable.ALARM_ID + "=" + id,
null);
} else {
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
DailyAlarmTable.ALARM_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Confirms that the columns the user requested exist.
* #param projection
*/
public void checkColumns(String[] projection) {
String[] available = { DailyAlarmTable.ALARM_ID,
DailyAlarmTable.ALARM_MEDNUM,
DailyAlarmTable.ALARM_TIMESTAMP,
DailyAlarmTable.ALARM_TIME };
if(projection != null) {
HashSet<String> requestedColumns = new HashSet<String>(Arrays.asList(projection));
HashSet<String> availableColumns = new HashSet<String>(Arrays.asList(available));
// Check if all columns which are requested are available
if(!availableColumns.containsAll(requestedColumns)) {
throw new IllegalArgumentException("Unknown columsn in projection");
}
}
}
}
Oh oh, okay I found that you return null in your query method of AlarmProvider class :)). Let's return cursor for this
If the onLoadFinished() method passes you a null cursor, then that means that the ContentProvider's query() method has returned null. You need to fix your query() method so that it doesn't return null in this case.
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.