SQLite selectionArgs not working - android

I am having some kind of weird problem with my content provider: selectionArgs refused to work for a particular column.
Insertion
void saveFile(File file, Cursor cursor, Context context) {
if (file.isDirectory()) {
throw new IllegalArgumentException(file.getName() + " must be a file");
}
values.put(IS_DIRECTORY, false);
// Some other values.put()
context.getContentResolver().insert(FolderContract.CONTENT_URI, values);
}
And
void saveFolder(File file, String filesNum, Context context, int directTracks) {
if (!file.isDirectory()) {
throw new IllegalArgumentException(file.getName() + " must be a directory");
}
ContentValues values = new ContentValues();
values.put(IS_DIRECTORY, true);
// Some other values.put()
context.getContentResolver().insert(FolderContract.CONTENT_URI, values);
}
My Query
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Timber.w("onCreateLoader: ");
String selection = String.format("%s = ? AND %s = ?", FolderContract.IS_DIRECTORY, FolderContract.PARENT_ID);
String[] selectionArgs = selectionArgs = new String[]{"0", args.getString(FOLDER_PATH)};
return new CursorLoader(getActivity(), FolderContract.CONTENT_URI, null, selection, selectionArgs, null);
}
I want the the query to return the rows with FolderContract.PARENT_ID column = args.getString(FOLDER_PATH) while the FolderContract.IS_DIRECTORY column will be false.
But the above query returns zero results however if I run it without the FolderContract.IS_DIRECTORY in the selection it returns all the matching
results.The weird thing is that if I loop through the cursor, some of the returned rows has its FolderContract.IS_DIRECTORY column as 0 while
others is 1.
ContentProvider
#Override
public Uri insert(#NonNull Uri uri, ContentValues values) {
final SQLiteDatabase database = dbHelper.getWritableDatabase();
long insertedRowId = database.insert(FolderContract.TABLE_NAME, null, values);
if (insertedRowId > -1) {
Uri insertedUri = ContentUris.withAppendedId(CONTENT_URI, insertedRowId);
getContext().getContentResolver().notifyChange(insertedUri, null);
return insertedUri;
}
database.close();
return uri;
}
#Override
public Cursor query(#NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase database = dbHelper.getReadableDatabase();
Cursor cursor = database.query(FolderContract.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
Please any idea what I am doing wrong?

Related

SQLite retrieve foreign key values with content provider

I have a sqlite database with "label" and "idea" tables in my Android app. made a foreign key on idea table as idea_label with int values that connected to label_table on its _id.
I use Loader to load my Cursor on the mainActivity that loads my idea table from the provider. As obvious it load idea_label int (But what I seek is to load the value from label_table which sets in label_body).
My loader on mainActivity class
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
cursor = database.query(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
Is there a way? trigger another loader? or my implementation is wrong in this case? any way or help that direct me to the right direction, will be very appreciated.
With SQLiteQueryBuilder you can join tables in setTables method and then just specify label_body column of label table in projection.
String[] projection = {
DatabaseContract.IdeaEntry._ID,
....
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
...
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables("IDEA JOIN LABEL ON IDEA.IDEA_LABEL = LABEL._ID");
builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
Thanks to #gar_r to help and guide me , I did like this with DatabaseContract constants:
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
String[] projection = {
DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + "." + DatabaseContract.IdeaEntry._ID,
DatabaseContract.IdeaEntry.COLUMN_IDEA_NAME,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DESCRIPTION,
DatabaseContract.IdeaEntry.COLUMN_IDEA_DATE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ICON,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ACTIVE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_FAVORITE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_DONE,
DatabaseContract.IdeaEntry.COLUMN_IDEA_IS_ARCHIVED,
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER,
DatabaseContract.LabelEntry.COLUMN_LABEL_BODY,
};
return new CursorLoader(this,
DatabaseContract.IdeaEntry.CONTENT_URI_IDEA,
projection,
null, // selection
null, // selectionArgs
DatabaseContract.IdeaEntry.COLUMN_IDEA_ORDER // order
);
}
That calls this section on my provider class
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case IDEAS:
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(DatabaseContract.IdeaEntry.IDEA_TABLE_NAME + " JOIN " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME
+ " ON " + DatabaseContract.IdeaEntry.COLUMN_IDEA_LABEL + " = " + DatabaseContract.LabelEntry.LABEL_TABLE_NAME + "." + DatabaseContract.LabelEntry._ID);
cursor = builder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
break;

How do implicit joined columns work with Android contacts data?

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.

Content provider uris from one app to other access

I have created a content uri from one app :
content://SendDataProvider/bookevent
i am able to insert the values into it.
like :
content://SendDataProvider/bookevent/1
content://SendDataProvider/bookevent/2 etc...
but when i call it to other app for geting all record from this URI i use
String bookContentDataLog = "content://SendDataProvider/bookevent";
Uri bookrecord = Uri.parse(bookContentDataLog);
Cursor cursor = BookShelfActivity.this.getContentResolver().query(
bookrecord, null, null, null, null);
if (cursor.moveToFirst()) { // I got Null pointer exception here
String sessionId = cursor.getString(cursor.getColumnIndex(BOOK_COLUMN_SESSIONID));
String magId = cursor.getString(cursor.getColumnIndex(BOOK_COLUMN_MAG_ID));
}
I debug it and i found that
BookShelfActivity.this.getContentResolver().query(
bookrecord, null, null, null, null);
this return null value to cursor.
How i get all value inserted to this "content://SendDataProvider/bookevent"?
============== I have Used method to insert to uri ===================
1) Class Name and variables
public class SendDataProvider extends ContentProvider {
public SendDataProvider(Context c) {
this.context = c;//getContext();
BookDb dbHelper = new BookDb(context);
db = dbHelper.getWritableDatabase();
}
2) content provider class methode :===
#Override
public Uri insert(Uri uri, ContentValues contentValues) throws NullPointerException {
Log.e("SendDataProvider #Override inserted call","Uri insert");
long rowID = db.insert(BOOK_TABLE_NAME, null, contentValues);
Log.e("SendDataProvider #Override inserted ID","Uri insert ID"+rowID);
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
this.context.getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to add a record into " + uri);
}
3) query
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(BOOK_TABLE_NAME);
switch (uriMatcher.match(uri)) {
case uriCode:
qb.setProjectionMap(this.values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (sortOrder == null || sortOrder == "") {
sortOrder = BOOK_COLUMN_MAG_ID;
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null,
null, sortOrder);
c.setNotificationUri(this.context.getContentResolver(), uri);
return c;
}
SOLVED :
Step 1:
create Provider class with DB inser/update/delte/query and be carefull about this things.
static final String PROVIDER_NAME = "com.example.contentprovideruser.DataProvider";//"org.geometerplus.android.fbreader.interfaces.SendDataProvider";
static final String URL = "content://" + PROVIDER_NAME + "/bookevent";
static final Uri CONTENT_URI = Uri.parse(URL);
You need to create well URI
Step 2: Add PROVIDER_NAME AndroidManifest.xml
<provider android:name=".DataProvider"
android:authorities="com.example.contentprovideruser.DataProvider"
android:exported="true"
android:multiprocess="true">
</provider>
Step 3:Need to specify well MIME TYPE
#Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK:
return "vnd.android.cursor.dir/vnd.booktype";
case BOOK_ID:
return "vnd.android.cursor.item/vnd.booktype";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
step 4 :"CONTENT_URI" is your global uri where all value are inserted
you can use query to get result.
Uri students = DataProvider.CONTENT_URI;
Cursor c = getContentResolver().query(students, null, null, null, "name");
if (c.moveToFirst()) {
do{
Toast.makeText(this,
c.getString(c.getColumnIndex(DataProvider.BOOK_COLUMN_EVENT_NAME)) +
", " + c.getString(c.getColumnIndex( DataProvider.BOOK_COLUMN_FROMPAGE_NO)),
Toast.LENGTH_SHORT).show();
} while (c.moveToNext());
}
BE happy :)

Using CursorLoader to get emails causes duplication of emails

I am trying to get email ids of uses contacts. For that I am using Cursor Loader. There is one problem I am getting duplicate email ids also. How to remove email duplicacy. Should I use raw query "SELECT DISTINCT" instead of using CursorLoader or there is some other solution?
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ;
//showing only visible contacts
String[] selectionArgs = null;
return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
I recently ran into this problem. It appears that the CursorLoader does not have an implementation of "DISTINCT". My workaround adds a few lines to the onLoadFinish method and extends the BaseAdapter to accept a List parameter:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String projection[] = {
CommonDataKinds.Phone._ID,
CommonDataKinds.Phone.DISPLAY_NAME,
};
String select = "((" + CommonDataKinds.Phone.DISPLAY_NAME + " NOTNULL) and " + CommonDataKinds.Phone.HAS_PHONE_NUMBER + " > 0)";
String sort = CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
CursorLoader loader = new CursorLoader(
mContext,
CommonDataKinds.Phone.CONTENT_URI,
projection,
select,
null,
sort
);
return loader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
List<String> displayNames = new ArrayList<String>();
cursor.moveToFirst();
while(!cursor.isAfterLast()){
String name = cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.DISPLAY_NAME));
if(!displayNames.contains(name))
displayNames.add(name);
cursor.moveToNext();
}
mAdapter.swapCursor(displayNames);
}
Here is my BaseAdapter class:
public class AdapterAddContacts extends BaseAdapter{
private List<String> mData = new ArrayList<String>();
private Context mContext;
public AdapterAddContacts(Context context,List<String> displayNames){
mData = displayNames;
mContext = context;
}
#Override
public int getCount() {
if(mData != null)
return mData.size();
else
return 0;
}
#Override
public Object getItem(int pos) {
return mData.get(pos);
}
#Override
public long getItemId(int id) {
return id;
}
#Override
public View getView(int pos, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.entry_add_contacts,parent,false);
String data = mData.get(pos);
TextView textName = (TextView)view.findViewById(R.id.my_contacts_add_display_name);
textName.setText(data);
textName.setTag(data);
return view;
}
public void swapCursor(List<String> displayNames){
mData = displayNames;
this.notifyDataSetChanged();
}
You should be able to modify this specifically for your needs.
Inspired by #mars, I have a solution that does not need a modification of the adapter. The idea is to delete the duplicates of the cursor; as there is no way to do it, we create a new cursor whithout the duplicates.
All the code is in onLoadFinished:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
MatrixCursor newCursor = new MatrixCursor(PROJECTION); // Same projection used in loader
if (cursor.moveToFirst()) {
String lastName = "";
do {
if (cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)).compareToIgnoreCase(lastName) != 0) {
newCursor.addRow(new Object[]{cursor.getString(0), cursor.getString(1), cursor.getString(2) ...}); // match the original cursor fields
lastName =cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
}
} while (cursor.moveToNext());
}
mContactsAdapter.swapCursor(newCursor);
}
I used a small hack in my project - an SQL injection, like that:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(
this,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] {
"DISTINCT "+ MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME},
null, null, null);
}
This code returns only bundle names and their IDs from Gallery.
So, I'd rewrite your code like that:
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = new String[] {
"DISTINCT " + ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Email.DATA};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ;
//showing only visible contacts
String[] selectionArgs = null;
return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
You can put setDistinct in your content provider.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setDistinct(true);
If you are worried about performance and don't want to play around with cursor again in onLoadFinished(), then there is a small hack
I combined following two solutions from SO.
select distinct value in android sqlite
CursorLoader with rawQuery
And here is my working solution:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String tableName;
/*
* Choose the table to query and a sort order based on the code returned
* for the incoming URI.
*/
switch (uriMatcher.match(uri)) {
case NOTIFICATION:
tableName = NOTIFICATIONS_TABLE_NAME;
break;
case NOTIFICATION_TIMESTAMP:
Cursor cursor = db.query(true, NOTIFICATIONS_TABLE_NAME, projection, selection, selectionArgs, TIMESTAMP, null, sortOrder, null);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
case DOWNLOAD:
tableName = DOWNLOADS_TABLE;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (selection != null) {
selection = selection + "=?";
}
Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
// Tell the cursor what uri to watch, so it knows when its source data
// changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
If you see in this case Table name is same is first 2 cases but i created a dummy Uri to achieve this. May not be a very good approach but works perfectly.
I found a solution
Use DISTINCT keyword in selection Array.
String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, "DISTINCT" + ContactsContract.CommonDataKinds.Email.DATA};

Android Mediastore: How to efficiently retrieve all songs of a certain genre?

I know how to retrieve the genre of a particular song, (see getting the genres), but I want to retrieve all songs of a particular genre. Since "genre" does not seem to be one of the columns for a media item, I don't know how to do it in a single query, unlike artist or album. Is there an efficient method? Thanx!
Uri uri = Audio.Genres.Members.getContentUri("external", genreID);
String[] projection = new String[]{Audio.Media.TITLE, Audio.Media._ID};
Cursor cur = contentResolver.query(uri, projection, null, null, null);
You can do this by putting together a content URI and querying the MediaStore. Here's some code borrowed from the Android music player:
String [] cols = new String [] {MediaStore.Audio.Genres.NAME};
Cursor cursor = MusicUtils.query(this,
ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
cols, null, null, null);
This is the code in MusicUtils:
public static Cursor query(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder, int limit) {
try {
ContentResolver resolver = context.getContentResolver();
if (resolver == null) {
return null;
}
if (limit > 0) {
uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();
}
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (UnsupportedOperationException ex) {
ErrorReporter.getInstance().putCustomData("UnsupportedOperationException", "true");
return null;
}
}

Categories

Resources