I need to read(query) a exact row of a column of my database. This is the relevant data of my provider:
public class TravelOrderProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/travelorder");
private static final int URI_TRAVELORDER = 1;
private static final int URI_TRAVELORDER_ITEM = 2;
private static final UriMatcher mUriMatcher;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, "travelorder", URI_TRAVELORDER);
mUriMatcher.addURI(AUTHORITY, "travelorder/#", URI_TRAVELORDER_ITEM);
}
public class TravelOrder implements BaseColumns {
public static final String NAME = "name";
public static final String GROUP = "group";
public static final String ORDER = "order";
}
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
database = mDbHelper.getWritableDatabase();
int match = mUriMatcher.match(uri);
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
qBuilder.setTables(TravelOrderDatabaseHelper.TABLE_NAME);
switch (match){
case URI_TRAVELORDER:
//nothing
break;
case URI_TRAVELORDER_ITEM:
String id = uri.getPathSegments().get(1);
qBuilder.appendWhere(TravelOrder._ID + "=" + id);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
Cursor c = qBuilder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
Now, this is what I'm trying to do in my Activity:
private static final String[] PROJECTION = {TravelOrder._ID, TravelOrder.NAME, TravelOrder.GROUP, TravelOrder.ORDER};
Cursor c = getContentResolver().query(TravelOrderProvider.CONTENT_URI, PROJECTION, null, null, null);
My question is: How can I get a especific row of the GROUP column? I now that this should be done in the last code line above, but I have tryed defining the column and the row in the selection and selectionArgs definitions without result.
Regarding the question:
How can I get a especific row of the GROUP column?
There is a pretty well explained example on how to "Read Information from a Database" on the Android training pages.
Quoting:
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_UPDATED,
...
};
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_UPDATED + " DESC";
Cursor c = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
So, by altering the selection and selectionArgs parameters accordingly, you can surely get a especific row of the GROUP column of your database.
For example, let's say that you want to get the rows that match an specific value of your GROUP column. So you should set up your query like this:
SQLiteDatabase db = mDbHelper.getReadableDatabase();
/* The columns used for the SELECT statement. */
String[] projection = {
"ID",
"GROUP"
};
/*
* The columns used for the WHERE statement,
* they should be formatted as a prepared statement.
*/
String selection = "`ID` = ? AND `GROUP` = ?";
/*
* The arguments that will be replaced for each ?
* in the above statement.
*/
String[] selectionArgs = {
desiredId, desiredGroup
};
String sortOrder = "`ID` ASC";
Cursor c = db.query(
"MY_TABLE", // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
Related
I am trying to write a simple content provider and populate a ListView using these references:
https://developer.android.com/reference/android/app/ListActivity.html
http://www.newthinktank.com/2015/01/make-android-apps-21/
I looked at this thread but it does not seem to be my issue:
SimpleCursorAdapter to populate ListView
The database seems to work, but when I try to bind to my ListView it gives an error of a missing column '_id', but I have it since I can log the contents of the database without problems. Code snippets below:
Logging the database (This WORKS!):
public void logAllPatients() {
// Projection contains the columns we want
String[] projection = new String[]{"id", "name"};
// Pass the URL, projection and I'll cover the other options below
Cursor cursor = resolver.query(CONTENT_URL, projection, null, null, null);
// Cycle through and display every row of data
if (cursor.moveToFirst()) {
do {
String patientList = "";
String id = cursor.getString(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
patientList = patientList + id + " : " + name + "\n";
Log.d(TEST_CONTENT_PROVIDER, patientList);
} while (cursor.moveToNext());
}
}
trying to populate the listview (missing column?, why)?
private void bindAllPatients() {
try {
// Projection contains the columns we want
String[] projection = new String[]{"id", "name"};
Cursor cursor = resolver.query(CONTENT_URL, projection, null, null, null);
if (cursor != null) {
startManagingCursor(cursor);
cursor.moveToFirst();
// Now create a new list adapter bound to the cursor.
// SimpleListAdapter is designed for binding to a Cursor.
ListAdapter adapter = new SimpleCursorAdapter(
this, // Context.
android.R.layout.two_line_list_item,
cursor, // Pass in the cursor to bind to.
new String[]{"id", "name"}, // Array of cursor columns to bind to.
new int[]{R.id.my_id, R.id.my_name}, 0);
// Parallel array of which template objects to bind to those columns.
// Bind to our new adapter.
setListAdapter(adapter);
cursor.close();
}
} catch (Exception e) {
Log.e(TEST_CONTENT_PROVIDER, e.toString());
}
}
The output log:
D/GMO_CONTENT_PROVIDER: 9 : Joe
D/GMO_CONTENT_PROVIDER: 10 : Mary
E/GMO_CONTENT_PROVIDER: java.lang.IllegalArgumentException: column '_id' does not exist
here's the database creation:
private SQLiteDatabase sqlDB;
static final String DATABASE_NAME = "myPatients";
static final String TABLE_NAME = "patients";
static final String CREATE_DB_TABLE = "CREATE TABLE " + TABLE_NAME +
"(id INTEGER PRIMARY KEY AUTOINCREMENT, " + " name TEXT NOT NULL);";
// bunch of code
#Override
public void onCreate(SQLiteDatabase sqlDB) {
try {
sqlDB.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
sqlDB.execSQL(CREATE_DB_TABLE);
} catch (Exception e) {
Log.e(TEST_CONTENT_PROVIDER, e.toString());
}
}
and the query override, which works!
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
switch (uriMatcher.match(uri)) {
case uriCode:
queryBuilder.setProjectionMap(values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
Cursor cursor = queryBuilder.query(sqlDB, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
any help is greatly appreciated!
The correct answer:
Android column '_id' does not exist?
"SimpleCursorAdapter requires that the Cursor's result set must include a column named exactly "_id"."
I'm querying the ContactsContract.Data table to find phone records.
I get an error when I create a new CursorLoader:
java.lang.IllegalArgumentException: Invalid column deleted
My code:
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
...
String[] projection = {
Phone.DELETED,
Phone.LOOKUP_KEY,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
Data.MIMETYPE,
Data.DISPLAY_NAME_PRIMARY
};
// "mimetype = ? AND deleted = ?"
String selection = Data.MIMETYPE + " = ? AND " Phone.DELETED + " = ?";
String[] args = {Phone.CONTENT_ITEM_TYPE, "0"};
return new CursorLoader(
this,
Data.CONTENT_URI,
projection,
selection,
args,
null);
Any idea why the Phone.DELETED column isn't included in the cursor? The documentation does say -
Some columns from the associated raw contact are also available
through an implicit join.
Looks like you've found a feature that has been documented in many places, but hadn't been implemented yet. I opened a bug for tracking this issue - lets see what AOSP guys have to say on the subject (bug report).
Meanwhile, you can use the following workaround:
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;
String[] projection = {
Phone._ID,
Phone.DELETED,
//Phone.LOOKUP_KEY,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
Data.MIMETYPE,
Data.DISPLAY_NAME_PRIMARY
};
String selection = Data.MIMETYPE + " = ? AND " + Data.DELETED + " = ?";
String[] args = {
Phone.CONTENT_ITEM_TYPE, "0"
};
return new CursorLoader(
this,
uri,
projection,
selection,
args,
null);
Changes:
Use RawContactsEntity's URI
LOOKUP_KEY is not accessible via above URI - you'll have to execute additional query if you absolutely need this column
_ID column will be required if you are going to use the resulting Cursor in CursorAdapter.
Edit: following #MichaelAlanHuff's request I'm posting the parts of code which this answer is based upon
From com.android.providers.contacts.ContactsProvider2#queryLocal() (source code of ContactsProvider2):
protected Cursor queryLocal(final Uri uri, final String[] projection, String selection,
String[] selectionArgs, String sortOrder, final long directoryId,
final CancellationSignal cancellationSignal) {
final SQLiteDatabase db = mDbHelper.get().getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String groupBy = null;
String having = null;
String limit = getLimit(uri);
boolean snippetDeferred = false;
// The expression used in bundleLetterCountExtras() to get count.
String addressBookIndexerCountExpression = null;
final int match = sUriMatcher.match(uri);
switch (match) {
...
case DATA:
case PROFILE_DATA:
{
final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
final int typeInt = getDataUsageFeedbackType(usageType, USAGE_TYPE_ALL);
setTablesAndProjectionMapForData(qb, uri, projection, false, typeInt);
if (uri.getBooleanQueryParameter(Data.VISIBLE_CONTACTS_ONLY, false)) {
qb.appendWhere(" AND " + Data.CONTACT_ID + " in " + Tables.DEFAULT_DIRECTORY);
}
break;
}
...
}
qb.setStrict(true);
// Auto-rewrite SORT_KEY_{PRIMARY, ALTERNATIVE} sort orders.
String localizedSortOrder = getLocalizedSortOrder(sortOrder);
Cursor cursor = query(db, qb, projection, selection, selectionArgs, localizedSortOrder, groupBy,
having, limit, cancellationSignal);
if (readBooleanQueryParameter(uri, Contacts.EXTRA_ADDRESS_BOOK_INDEX, false)) {
bundleFastScrollingIndexExtras(cursor, uri, db, qb, selection,
selectionArgs, sortOrder, addressBookIndexerCountExpression,
cancellationSignal);
}
if (snippetDeferred) {
cursor = addDeferredSnippetingExtra(cursor);
}
return cursor;
}
As you can see, there are two additional methods where SQLiteQueryBuilder used to build the query could be changed: setTablesAndProjectionMapForData() and additional query() method.
Source of com.android.providers.contacts.ContactsProvider2#setTablesAndProjectionMapForData():
private void setTablesAndProjectionMapForData(SQLiteQueryBuilder qb, Uri uri,
String[] projection, boolean distinct, boolean addSipLookupColumns, Integer usageType) {
StringBuilder sb = new StringBuilder();
sb.append(Views.DATA);
sb.append(" data");
appendContactPresenceJoin(sb, projection, RawContacts.CONTACT_ID);
appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);
appendDataUsageStatJoin(
sb, usageType == null ? USAGE_TYPE_ALL : usageType, DataColumns.CONCRETE_ID);
qb.setTables(sb.toString());
boolean useDistinct = distinct || !ContactsDatabaseHelper.isInProjection(
projection, DISTINCT_DATA_PROHIBITING_COLUMNS);
qb.setDistinct(useDistinct);
final ProjectionMap projectionMap;
if (addSipLookupColumns) {
projectionMap =
useDistinct ? sDistinctDataSipLookupProjectionMap : sDataSipLookupProjectionMap;
} else {
projectionMap = useDistinct ? sDistinctDataProjectionMap : sDataProjectionMap;
}
qb.setProjectionMap(projectionMap);
appendAccountIdFromParameter(qb, uri);
}
Here you see the construction of table argument of the final query using StringBuilder which is being passed to several append*() methods. I'm not going to post their source code, but they really join the tables that appear in methods' names. If rawContacts table would be joined in, I'd expect to see a call to something like appendRawContactJoin() here...
For completeness: the other query() method that I mentioned does not modify table argument:
private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
String selection, String[] selectionArgs, String sortOrder, String groupBy,
String having, String limit, CancellationSignal cancellationSignal) {
if (projection != null && projection.length == 1
&& BaseColumns._COUNT.equals(projection[0])) {
qb.setProjectionMap(sCountProjectionMap);
}
final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having,
sortOrder, limit, cancellationSignal);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI);
}
return c;
}
The inspection of the above chain of methods led me to the conclusion that there is an officially documented feature which is not implemented.
I have created two string variables, 'questionString' and 'answerSetId' - the first of which I have assigned to a textview - the second of which I would like to pass to my db helper class:
public void showNextRandomQuestion3() {
SQLDatabaseHelper db = new SQLDatabaseHelper(this);
//get the data from the database
List<List<String>> listList = db.getAllAnswersByQuestion1();
//Get the question/text/answer Strings
List<String> questionStrings = listList.get(0); //question Strings
List<String> answerSetIds = listList.get(4);
//Generate random index
Random r = new Random();
int rand = Math.abs((r.nextInt() % questionStrings.size()));
//get answer description for randomly selected question
String questionString = questionStrings.get(rand);
String answerSetId = answerSetIds.get(rand);
questionView.setText(questionString);
}
In my db helper class, I want to use this string variable as pat of my query:
public List<List<String>> getAnswers() {
List<String> array1 = new ArrayList<String>();
List<String> array2 = new ArrayList<String>();
SQLiteDatabase db = this.getReadableDatabase();
String selectQuery = "SELECT * FROM " + TABLE_ANSWERS + "WHERE " + ASID
+ " = " + ??answerSetId??;
Cursor c = db.rawQuery(selectQuery, null);
if (c.moveToFirst()) {
do {
String textdescr = c.getString(c.getColumnIndex(TDESCR));
String answersdescr = c.getString(c.getColumnIndex(ADESCR));
array1.add(textdescr);
array2.add(answersdescr);;
} while (c.moveToNext());
}
List< List<String> > listArray2 = new ArrayList< List<String> >();
listArray2.add(array1);
listArray2.add(array2);
return listArray2;
}
What would be the best way to achieve this?
In order to perform a SELECT, you can use this method
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
instead of raw query. In your case:
String selection = ASID+"=?";
String selectionArgs[] = new String[]{String.valueOf(answerSetId)};
Cursor c = db.query(TABLE_ANSWERS, null, selection, selectionArgs, null, null, null);
You can also select some specific columns, passing a list of columns name as second parameter (insted of null). Also, you can replace the last three null value in the example for adding the GROUP BY, HAVING and ORDER BY clauses.
For example, in your query you are interested only in the two columns TDESCR and ADESCR. So you could use this query:
String[] columns = new String[]{TDESCR, ADESCR};
String selection = ASID+"=?";
String[] selectionArgs = new String[]{String.valueOf(answerSetId)};
Cursor c = db.query(TABLE_ANSWERS, columns, selection, selectionArgs, null, null, null);
There are a lot of others override methods query. Take a look at the documentation here
However, if you prefer you can perform a raw query in this way:
String queryString = "SELECT * FROM " + TABLE_ANSWERS +
" WHERE "+ASID+"=?";
String[] selectionArgs = new String[]{String.valueOf(answerSetId)};
sqLiteDatabase.rawQuery(queryString, selectionArgs);
I would like to write a ContentObserver for my app's local table. Also I have ContentProvider to access the table. Now in one of my Activity i have to observe for a change in only one row of that table. I have the primary key to observe for it but the primary key is a TEXT not INTEGER. When googled on it I found about getting specific row's Uri by using ContentUris.withAppendedId method. But it requires id must be long(NUMBER). So is there a way I can get Uri of single row of a table when the id is TEXT?
From the comments of #pskink I found the way to achieve it and would like to share it. In the existing content provider's uri matcher we have to add '/*' with new entry as follows...
private static final String AUTH = "com.test.Provider";
public static final Uri TABLE_URI = Uri.parse("content://" + AUTH + "/"
+ TABLE_NAME);
public static final Uri TABLE_ID_URI = Uri.parse("content://" + AUTH
+ "/" + TABLE_NAME + "/*");
final static int TABLE_COMMENT = 10;
final static int TABLE_ROW_COMMENT = 11;
uriMatcher.addURI(AUTH, TABLE_NAME, TABLE_COMMENT);
uriMatcher.addURI(AUTH, TABLE_NAME + "/*", TABLE_ROW_COMMENT);
And in the query method as follows...
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String orderBy) {
db = dbManager.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case TABLE_COMMENT:
cursor = db.query(TABLE_NAME, projection, selection,selectionArgs, null, null, orderBy);
if (cursor != null)
cursor.setNotificationUri(getContext().getContentResolver(),uri);
break;
case TABLE_ROW_COMMENT:
String id = uri.getLastPathSegment();//getting the PK
cursor = db.query(TABLE_NAME, projection,Table._ID + "=?", new String[] { id },null, null, orderBy);
if (cursor != null)
cursor.setNotificationUri(getContext().getContentResolver(),uri);
break;
}
return cursor;
}
And from the activity or fragment can get a specific row's uri from the loader as follows...
Uri uri = Uri.withAppendedPath(Provider.TABLE_URI,
"pk value");
Cursor cursor = getContentResolver().query(uri, null, null, null,
null);
And in my case I'll use this Uri for Observer.
I am trying to UNION two tables with the same fields to create a single cursor (through a content provider) that I am using to create my ListView.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String groupBy = null;
switch (sUriMatcher.match(uri)) {
case LIST:
StringBuilder sb = new StringBuilder();
for (String s : projection)
sb.append(s).append(",");
String projectionStr = sb.toString();
projectionStr = projectionStr.substring(0,
projectionStr.length() - 1);
String[] subQueries = new String[] {
"SELECT " + projectionStr + " FROM " + Customer.TABLE_NAME,
"SELECT " + projectionStr + " FROM "
+ IndividualCustomer.TABLE_NAME };
String sql = qb.buildUnionQuery(subQueries, sortOrder, null);
SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
Cursor mCursor = db.rawQuery(sql, null);
mCursor.setNotificationUri(getContext().getContentResolver(), uri);
return mCursor;
Even if the two tables are empty, I get two null rows, which creates two rows in my listview. How do I get rid of this problem?
Additionally, when I delete a row from the ListView, the cursor is not getting updated in spite of setNotificationUri()
Any pointers, will be most appreciated
Solved - I had to supply a group by clause as one of the columns (of the projection) had a "TOTAL(...)" function.