Distinct Query For CursorLoader - android

I am using CursorLoader to populate my listView.
But I need to get distinct result set.
But CursorLoader has no constructor to set distinct parameter.
Any Suggestions?

Yes!
In the CursorLoader constructor you specify a URI. Part of that URI is the ContentProvider Authority. The rest, though, is up to you! So create a virtual table!
If you have a table "songbirds", perhaps your URI is:
content://my.content.provider/songbirds
Simply teach your ContentProvider (add one more clause to your UriMatcher) to recognize the URI:
content://my.content.provider/distinctSongbirds
Process the "distinct" URI exactly as you do the non-distinct one, except add a "distinct" param to the actual DB query, when you make it.

Related

Android Content Provider - Using Query Method To Insert, Delete, and Update

I have been studying Content Providers by reading Android Developer Fundamentals (Version 1).
Under section "11.1 Share Data Through Content Providers" / "The query() method" there is a note that states
Note: The insert, delete, and update methods are provided for
convenience and clarity. Technically, the query method could handle
all requests, including those to insert, delete, and update data.
How can query method be used to insert / delete / and update data? The query method has the following signature and does not take in custom SQL strings.
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder){}
I have not been able to find any resources that shows how this can be done. All seems to utilize the provided methods.
Frankly, that is a really poor job in that training guide. All it will do is confuse people.
I presume what they mean is that there is nothing magic about query() that forces it to only perform SQL SELECT statements. A ContentProvider is a facade — you can use it to store and retrieve from pretty much whatever you want, not just SQLite.
So, you could have your query() method:
Examine the Uri
Look for it to be of the form content://your.authority/something/insert/put/data/here
Parse the Uri to get the put, data, and here values
Insert those in a table under some pre-determined columns
Return an empty MatrixCursor
Or, it could:
Examine the Uri
Look for it to be of the form content://your.authority/something/insert
Insert a row using the projection for the columns and the selectionArgs as the values to put in those columns
Return an empty MatrixCursor
I do not know why anyone would do that, but it is certainly possible.

What is the right way to change the display name in an Android FileProvider?

Say if I have filename 1234567.png on the internal filesystem - say I want to send it to the user with a share intent as CoolPicture.png - ie subclass fileprovider and replace the query method.
Is using a matrix cursor based on the cursor from super a good plan?
It seems like DISPLAY_NAME and SIZE are the only columns but doesn't seem to confirm if SIZE is int or string.
http://developer.android.com/reference/android/provider/OpenableColumns.html
http://developer.android.com/reference/android/support/v4/content/FileProvider.html
I'll plan to post my code after I answer it, I just wanted to get an idea of best practice, and perhaps contribute.
Thanks
Is using a matrix cursor based on the cursor from super a good plan?
You could do that, and it's probably OK. After all, FileProvider itself uses a MatrixCursor.
To modify an existing Cursor, I use CursorWrapper myself, in my LegacyCompatCursorWrapper, then in query() wrap the Cursor I get from the base ContentProvider implementation (e.g., FileProvider) in my wrapper.
It seems like DISPLAY_NAME and SIZE re the only columns but doesn't seem to confirm if SIZE is int or string.
That's one of the reasons I went with the CursorWrapper, to avoid messing with any existing values.
That being said, the existing FileProvider implementation uses a Long (file.length(), auto-boxed).
This is an old question, but the new androidx.core.content.FileProvider has now a second getUriForFile method with an additional displayName param which sets the value for the OpenableColumns#DISPLAY_NAME key-column.

How to properly insert values into the SQLite database using ContentProvider's insert() method through a CursorLoader?

I was reading the doc, but I am still not too sure. Its says to use getContentResolver(), but then that really isn't using CursorLoader. So is there a way to do it through CursorLoader? I know how to do it with query(). Are the steps very similar? Even just a link that explains exactly this would be helpful.
Please note, do not link me to the Google doc as they do not have an example that ever uses the insert() method from ContentProvider using a CursorLoader.
Thanks in advance!!
Edit: I should probably mention the reason I am confused with this is because calling a new CursorLoader automatically calls ContentProviders query() method. But how can I do the same for insert?
Check out my blog post on the subject:
Content Resolvers and Content Providers
The CursorLoader has nothing to do with it.
Insertion is a totally different concept... it has absolutely nothing to do with the CursorLoader. When coupled with the LoaderManager, the CursorLoader automatically queries your database and updates itself when the ContentObserver is notified of a datastore change. It has nothing to do with the actual process of inserting data into your database.
How requests to the ContentResolver are resolved
When you insert (or query or update or delete) data into your database via the content provider, you don't communicate with the provider directly. Instead, you use the ContentResolver object to communicate with the provider (note that the ContentResolver is a private instance variable in your application's global Context) . More specifically, the sequence of steps performed is:
You call getContentResolver().insert(Uri, ContentValues);
The ContentResolver object determines the authority of the Uri.
The ContentResolver relays the request to the content provider registered with the authority (this is why you need to specify the authority in the AndroidManifest.xml).
The content provider receives the request and performs the specified operation (in this case insert). How and where the data is inserted depends on how you implemented the insert method (ContentProvider is an abstract class that requires the user to implement insert, query, delete, update, and getType).
Hopefully you were able to wrap your head around that at least a little. The reason why there are so many steps involved is because Android (1) allows applications to have more than one content provider, and (2) needs to ensure that apps can securely share data with other third-party apps. (It wasn't because it wanted to confuse you, I promise).
Inserting data via the ContentProvider
Now that you (hopefully) have a better idea of how the ContentResolver is able to relay these requests to the content provider, inserting the data is fairly straight forward:
First, decide which uri you want to have matched by your content provider. This depends on how you decided to match your uris with the UriMatcher. Each uri you have represents a different means of inserting data into your internal database (i.e. if your app has two tables, you will probably have two uris, one for each table).
Create a new ContentValues object and use it to package the data you wish to send to the content provider. The ContentValues object maps column names to data values. In the below example, the column name is "column_1" and the value being inserted under that column is "value_1":
ContentValues values = new ContentValues();
values.put("column_1", "value_1");
Once received, the content provider will (in your case) pass the values object to your SQLiteDatabase (via the SQLiteDatabase.insert(String table, String nullColumnHack, ContentValues values) method). Unlike the ContentProvider, this method is implemented for you... the SQLiteDatabase knows how to handle the values object and will insert the row into the database, returning the row id of the inserted row, or -1 if the insertion failed.
... and that's how you insert data into your database.
TL;DR
Use getContentResolver().insert(Uri, ContentValues);
Convert Cursor to ContentValues for easy database insertion.
Its says to use getContentResolver(), but then that really isn't using CursorLoader
Besides what Alex said, there's nothing preventing you from iterating through the Cursor returned, putting them into ContentValues and then inserting that (say, to a different DB).
Just an example from the top of my head (a simple Cursor of two String columns):
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.moveToFirst()) {
ArrayList<ContentValues> values = new ArrayList<ContentValues>();
do {
ContentValues row = new ContentValues();
DatabaseUtils.cursorStringToContentValues(cursor, fieldFirstName, row);
DatabaseUtils.cursorStringToContentValues(cursor, fieldLastName, row);
values.add(row);
} while (cursor.moveToNext());
ContentValues[] cv = new ContentValues[values.size()];
values.toArray(cv);
getContentResolver().bulkInsert(CONTENT_NAMES, cv);
}
}
There are probably more efficient ways to do that (and definitely some integrity checks), but that was my first thought...

Content Provider filtering query, filtering Cursor

I got following problem, I need to use a Content Provider to read a
Database of an other App.
first I want all rows, and after analyzing the data only e.g. the rows
from _id = 1, 3 and 5.
how can I call a Content provider and select only these rows?
or is it possible to create a subset Cursor form an given Cursor?
Thanks in advance.
If you're talking to another app, I assume you're querying the other app's ContentProvider to get the data from them in the first place.
In this situation, the cleanest answer seems not to build your own ContentProvider that filters/wraps theirs. Instead query their ContentProvider from your application directly, and use the select clause in your query() to specify the conditions that define the subset of data you want to be given.

how add limit clause to manageQuery on android

Android's API provides a clean mechanism via SQLite to make queries into the contact list. However, I am not sure how to limit the results:
Cursor cur = ((Activity)mCtx).managedQuery(
People.CONTENT_URI,
columns,
"LIMIT ? OFFSET ?",
new String[] { Integer.toString(limit), Integer.toString(offset) },
null
);
Doesn't work.
Actually, depending on the provider you can append a limit to the URI as follows:
uri.buildUpon().appendQueryParameter("limit", "40").build()
I know the MediaProvider handles this and from looking at recent code it seems you can do it with contacts too.
You are accessing a ContentProvider, not SQLite, when you query the Contacts ContentProvider. The ContentProvider interface does not support a LIMIT clause directly.
If you are directly accessing a SQLite database of your own, use the rawQuery() method on SQLiteDatabase and add a LIMIT clause.
I found out from this bug that Android uses the following regex to parse the LIMIT clause of a query:
From <framework/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java>
LIMIT clause is checked with following sLimitPattern.
private static final Pattern sLimitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
Note that the regex does accept the format offsetNumber,limitNumber even though it doesn't accept the OFFSET statement directly.
I think you have to do this sort of manually. The Cursor object that is returned from the managedQuery call doesn't execute a full query right off. You have to use the Cursor.move*() methods to jump around the result set.
If you want to limit it, then create your own limit while looping through the results. If you need paging, then you can use the Cursor.moveToPosition(startIndex) and start reading from there.
You can specify the "limit" parameter in the "order" parameter, maybe even inside other parameters if you don't want to sort, because you'll have to specify a column to sort by then:
mContentResolver.query(uri, columnNames, null, null, "id LIMIT 1");

Categories

Resources