I'm wanting to know what the correct way of passing parameters for a Database query using the ContentProvider pattern is.
Is it as follow - i.e. where & args in the update method.
getContentResolver().update(uri, cv,where, args);
Or is it by appending the values at the end of the URI - As in this example in the docs - see case 2?
public class ExampleProvider extends ContentProvider {
......
......
// Implements ContentProvider.query()
public Cursor query(
Uri uri, String[] projection,String selection, String[] selectionArgs,
String sortOrder) {
switch (sUriMatcher.match(uri)) {
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;
case 2:
selection = selection + "_ID = " uri.getLastPathSegment();
break;
And which of these approaches is the best. I've seen both used, but unsure of their respective merits.
Related
i have constructed a basic content provider that stores SMS messages for learning purposes, so far i can read(without selection args), insert, update and delete.
However i have been stumped trying to figure out how to format the selection args for the WHERE clause in my provider:
Basicly my application needs to search for a specific timestamp (in long format) and return its _id
say your database has an entry like this that your trying to access:
2|1|1410293471300|test type 1||testing|0
and the entire database looks like this:
_id|CLIENTTRXID|CREATED_AT|TYPE|MESSAGEPRIO|MESSAGE|ACCEPTED
1|1|1410293471000|test type 1||testing|0
2|1|1410293471300|test type 1||testing|0
3|1|1410293471600|test type 1||testing|0
in sql the query would be
"select _id from alerts where CREATED_AT=1410293471300;"
the code i was hoping would do the equivalent:
//normally i would get the string dynamically but to make it equal to the sql
String date = "1410293471300";
String[] selectionArgs = new String[]{ date };
Cursor cursor = getContext().getContentResolver().query(AlertContract.CONTENT_URI, null, AlertContract.Column.CREATED_AT, selectionArgs, AlertContract.DEFAULT_SORT);
seems to always produce the following error no matter what i try as selectionArgs
Exception caught﹕ Cannot bind argument at index 1 because the index is out of range. The statement has 0 parameters.
here is the query method of my contentprovider:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables( AlertContract.TABLE);
switch (sURIMatcher.match(uri)) {
case AlertContract.STATUS_DIR:
break;
case AlertContract.STATUS_ITEM:
qb.appendWhere(AlertContract.Column.ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException( "illegal uri: " + uri);
}
String orderBy = (TextUtils.isEmpty(sortOrder)) ? AlertContract.DEFAULT_SORT : sortOrder;
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
//register for uri changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Log.d(TAG, "queried records: "+cursor.getCount());
return cursor;
}
Presumably im missing something extremely obvious, and will feel quite silly for having posted this question.
But for the moment i would very much appreciate any help, as i am quite stumped.
It looks like your issue is with your selection, rather than with your selectionArgs per se. The selection should be the whole query after the "where". Here your selection is "CREATED_AT". You need two more items to get it to work:
an =, since you want equality (you can also do other operators, of course)
a ?. This is where your selectionArgument will be inserted (each argument needs a ? in the selection, so there should be the same number of ?s in the selection as selectionArguments.
The end result should be more like "CREATED_AT = ?"
Check out the documentation and this tutorial for more info on how to correctly construct a ContentProvider query.
When you query the content provider, try the following. The selection should be AlertContract.Column.CREATED_AT + "=?"
Cursor cursor = getContext().getContentResolver().query(AlertContract.CONTENT_URI, null, AlertContract.Column.CREATED_AT + "=?", selectionArgs, AlertContract.DEFAULT_SORT);
i have implemented update() of ContentProvider and notifying to observer using getContext().getContentResolver().notifyChange(uri, null);
my obvious need is that whenever just one row is effected i want to notify with row specific uri, but could not find way to do so.
an additional query like "select id where selectionArgs" can do this but this will be a foolish way.
onchange(boolean, uri) get complete uri instead of specific row, easy to understand that this is because ContentProvider.update() is sending the same.
some code for more clarity
update() method of MyContentProvider
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d("TAG", "update " + uri.getPath());
int count = 0;
switch (uriMatcher.match(uri)) {
case BOOKS:
count = booksDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(DATABASE_TABLE, values,
_ID + " = " + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count == 1) {
Cursor c = query(uri, new String[] { _ID }, selection, selectionArgs, null);
long rowId = Long.valueOf(c.getString(c.getColumnIndex(_ID)));
uri = ContentUris.withAppendedId(CONTENT_URI, rowId);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
i will update table some how like
getContentResolver().update(MyContentProvider.CONTENT_URI, values1, MyContentProvider._ID+"<?", new String[]{"3"}));
frankly saying, code has barely related to question, just trying to give you some context
In your provider method, just return the uri with the id appended
#Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "insert " + uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = URI_MATCHER.match(uri);
Uri returnUri;
switch (match) {
case MESSAGE: {
long _id = db.insert(MessageContract.MessageEntry.TABLE_NAME, null, values);
if (_id > 0)
returnUri = ContentUris.withAppendedId(MessageContract.MessageEntry.CONTENT_URI, _id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(returnUri, null);
return returnUri;
}
And register your observer with true for descendents.
getContentResolver().registerContentObserver(MessageContract.MessageEntry.CONTENT_URI, true, mContentObserver);
To get the id from a Uri you can use ContentUris.parseId(uri)
Unfortunately I'm not able to suggest easy solution (because I'm not aware of full code and updates You need to run), there's some ways we You could try (some of them I've implemented in mine applications):
Provide ids in ContentValues - this way looks not applicable for Your case and it needs loop with calls to notifyChange();
Provide specific Uri for requests with queries (only some specific apps needs many various queries in selection, usually it's much easier to include query parameter in Uri). After another part of the program get notification with that specific Uri it will be able to check if it's 'current item' was updated and act appropriately (e.g. simplest case with list of articles and one article open in separate activity; then You update list of articles in the background from server You might need to update currently open article also and so, need to know if it was updated). You should be able to check particular item on the side of the observer using just received Uri, because it (Uri) will contain parameter(s) You've used for query;
You can pass the ID via ContentValues, and append it to the notification url. This way you don't have to make a separate query.
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int rows = _database.update(getTableName(), values, selection, selectionArgs);
if (rows > 0) {
Uri itemUri = ContentUris.withAppendedId(uri, values.getAsLong(DatabaseModel.COLUMN_ID)); // DatabaseModel.COLUMN_ID is "_id"
getContext().getContentResolver().notifyChange(itemUri, null);
}
return rows;
}
I am using my custom ContentProvider to communicate with sqlite database. I would like to display on a list (using ListFragment), data that comes from two tables (with many to many relation). The only solution I can think of for such case is to use rawQuery. And the questions is, if it is a good practice, or should I solve this in some other way?
Example of tables:
Table A: ID, COLUMN_FROM_A
Table B: ID, COLUMN_FROM_B
Joining table AB: ID, FK_ID_A, FK_ID_B
Example of overridden query method in ContentProvider:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor cursor = null;
int uriType = URIMatcher.match(uri);
switch (uriType) {
case TABLE_A_URI:
queryBuilder.setTables("TABLE_A");
cursor = queryBuilder.query(databaseHelper.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
break;
case TABLE_B_URI:
queryBuilder.setTables("TABLE_B");
cursor = queryBuilder.query(databaseHelper.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
break;
case TABLE_JOIN_A_B_URI:
cursor = databaseHelper.getReadableDatabase().rawQuery("select a.COLUMN_FORM_A, b.COLUMN_FROM_B from TABLE_A a, TABLE_B b, TABLE_AB ab where ab.FK_ID_A=a.ID and ab.FK_ID_B=b.ID", null);
break;
default:
throw new IllegalArgumentException("Unknown URI");
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
It's a good and common practice, very appropriate in this case.
I don't foresee any problems, we have used it in many apps.
How do I define a GROUP BY query for my CursorLoader?
The two constructors for a CursorLoader I see take either a single Context or a Context, Uri, projection, selection, selectionArgs and sortOrder.
But no groupBy.
(I'm using the support package for a Android 2.3 device)
Not really...
You can define a specific URI to your specific GROUP BY clause.
For example, if you have a table mPersonTable, possibly grouped by gender, you can define the following URIs:
PERSON
PERSON/#
PERSON/GENDER
When querying, switch between your queries so you can add your group by parameter:
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String groupBy = null;
switch (mUriMatcher.match(uri)) {
case PERSON_ID:
...
break;
case PERSON_GENDER:
groupBy = GENDER_COLUMN;
case PERSON:
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(mPersonTable);
builder.setProjectionMap(mProjectionMap);
return builder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder, limit);
default:
break;
}
}
In fact, you could pass any sort of parameters to your query
Obs.: Use a UriMatcher to match the uri with your query implementation.
You can add Group by with selection parameter
new CursorLoader(context,URI,
projection,
selection+") GROUP BY (coloum_name",
null,null);
Apparently (and this is a bit embarrassing) the very first line in the documentation clearly states that the CursorLoader queries the ContentResolver to retrieve the Cursor. While the ContentResolver doesn't expose any means to GROUP BY there is, hence, no way the CursorLoader could expose such functionality either.
So the apparent answer to my very own question is: You can't!
I'm looking into implementing CursorLoader in my app but I'm having a small issue that it seems that there isn't a way to just a pass a raw query to the CursorLoader constructor.
I maybe missing something in the documentation (and google), so if anybody can point me to a simple way to run a raw query with a CursorLoader class I would appreciate it. Otherwise I will have to probably create my own CursorLoader class with the necessary functionality, which I'm trying to avoid.
it seems that there isn't a way to just a pass a raw query to the CursorLoader constructor.
That is because CursorLoader works with content providers, and content providers do not support rawQuery().
so if anybody can point me to a simple way to run a raw query with a CursorLoader class I would appreciate it.
That is impossible, sorry. You are welcome to create your own AsyncTaskLoader that hits a SQLite database and supports rawQuery(). In fact, I will probably write one of these later this year, if I don't see where anyone has beaten me to it.
Raw query is not supported directly, but you can do a dirty hack: from your code call
getContentResolver().query(RAWQUERY_CONTENT_URI, null, rawquery, args, null);
and implement content provider like
#Override
public synchronized Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
int uriType = sURIMatcher.match(uri);
switch (uriType)
{
case RAW_QUERY:
return dbHelper.getReadableDatabase().rawQuery(selection, selectionArgs);
}
[...]
}
**For Custom Search using Content provider **
Change Cursor Loader as Follow (in onCreateLoader )
return new CursorLoader(
getActivity(), // Context
PRODUCT.CONTENT_URI, // URI
PROJECTION, // Projection
PRODUCT.PRODUCT_NAME+ " like ?", // Selection
new String[]{"%" + mCurFilter + "%"}, // Selection args
PRODUCT.PRODUCT_NAME + " asc");
In your Provider Change Accordingly
//C is Cursor object
switch (uriMatch) {
case ROUTE_PRODUCT_ID:
// Return a single entry, by ID.
String id = uri.getLastPathSegment();
builder.where(PRODUCT._ID + "=?", id);
c = builder.query(db, projection, sortOrder);
assert ctx != null;
c.setNotificationUri(ctx.getContentResolver(), uri);
return c;
// break;
case ROUTE_PRODUCT:
// Return all known entries.
builder.table(PRODUCT.PRODUCT_TABLE_NAME)
.where(selection, selectionArgs);
c = builder.query(db, projection, sortOrder);
assert ctx != null;
c.setNotificationUri(ctx.getContentResolver(), uri);
return c;
You can implement your own CursorLoader with raw query. This is the source of the original CursorLoader: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/CursorLoader.java