I have created a small application, trying to understand the functionality of the LoaderManager and CursorLoader-classes.
I have implemented LoaderCallbacks<Cursor> on my FragmentActivity-class and everything works fine, except the fact that when I update my data via ContentResolver.update() or ContentResolver.insert()-methods, onLoadFinished() is not called and as a result my data doesn't update.
I have a custom ContentProvider and I am wondering if the problem is in my ContentProvider not notifying that the data changed or something else.
Did you call setNotificationUri(ContentResolver cr, Uri uri) on the Cursor before returning it in ContentProvider.query()?
And did you call getContext().getContentResolver().notifyChange(uri, null) in the 'insert' method of your ContentProvider?
EDIT:
To get a ContentResolver call getContext().getContentResolver() in your ContentProvider.
Also check if you call somewhere cursor.close(), because in this case you unregister the content observer which was registered by CursorLoader. And the cursor closing is managed by CursorLoader.
Accepted answer was the little bit tricky to understand so I am writing the answer to make it easy for other developers..
Go to the class in which you have extended the ContentProvider
Find the query() method which has the following syntax
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
Write this line where you are returning the cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
In the end, my query method looks like this
#Nullable
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor;
cursor = noticeDbHelper.getReadableDatabase().query(
NoticeContract.NoticeTable.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
//This line will let CursorLoader know about any data change on "uri" , So that data will be reloaded to CursorLoader
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}`
Related
I have registered a CursorLoader, but it is not receiving updates from my ContentProvider
The sequence of events is:
In a Fragment, register the CursorLoader with:
getLoaderManager().initLoader(LOADER_FAVS_ID, null, this);
Note I am using the support library version ,so this method is android.support.v4.app.Fragment.getLoaderManager()
The CursorLoader is registered, and a Cursor is loaded in the onLoadFinished:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
Log.i(TAG, "onLoadFinished");
switch (loader.getId()) {
case LOADER_FAVS_ID:
Log.i(TAG,
"cursor notification uri: " + cursor.getNotificationUri());
mCursorAdapter.swapCursor(cursor);
break;
}
}
Which Logs cursor notification uri: content://com.myapp.mylocation/db_locations, for example. This is because I made sure to call cursor.setNotificationUri(getContext().getContentResolver(), uri); before returning the Cursor from my ContentProvider.
Also note that my content provider returns a MergeCursor.
Some time later, a call is made to update() in my ContentProvider, and the following lines are executed:
Log.i(TAG,
"Notifying uri: " + uri.toString());
getContext().getContentResolver().notifyChange(
uri, null);
Which Logs Notifying loc uri: content://com.myapp.mylocation/db_locations, the same uri as above.
But onLoadFinished is never called, and my Cursor is never updated. I believe I have followed the advice I can find, all of which is basically this. Why else would onLoadFinished not be called after all of this?
Solved it, but since I haven't seen this documented, here's my solution.
I was returning a MergeCursor from my ContentProvider, basically just concatenating a list of cursors. I had
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Generate an array of Cursors
MergeCursor mergCursor = new MergeCursor(cursorArray);
// notify potential listeners
mergCursor.setNotificationUri(getContext().getContentResolver(), uri);
return mergCursor;
}
And my CursorLoader was never receiving the notification. However, as MergeCursor is basically just an Array of Cursor, you need to set the notification uri on each cursor in your MergeCursor.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Generate an array of Cursors
// set the notification uris...
for (Cursor cursor : cursorArray) {
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
MergeCursor mergCursor = new MergeCursor(cursorArray);
return mergCursor;
}
Now everything works as expected!
I have an app that stores user search history. I am now adding Custom Suggestions as well. This required me to add one more method, query() and override it in my MyCustomSuggestionProvider class.
However, doing this prevents Recent Suggestions from showing. They are still being stored in the suggestions.db. The moment I delete the query() method to test, recent suggestions come back.
Does anyone know how to make these two things work together?
The problem was not overiding query(), it was how I was handling it. Here is my solution:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor recentCursor = super.query(uri, projection, selection,
selectionArgs, sortOrder);
query = selectionArgs[0];
if (query == null || query.length() < 3) {
return recentCursor;
} else {
query = selectionArgs[0];
MatrixCursor customCursor = new MatrixCursor(COLUMN_NAMES);
SearchSuggestionObject[] suggestions = getSuggestionsFromWebFeed(query);
for (SearchSuggestionObject suggestion : suggestions) {
customCursor.addRow(new Object[] { suggestion.getId(),
suggestion.getItem(), suggestion.getCat(),
suggestion.getItem(), suggestion.getItem(),
suggestion.getCat(), "android.intent.action.SEARCH" });
}
return customCursor;
}
}
this may sound like a silly question but Im struggling with it. I am working on a quotes application therefore I want to have my quotes ordered from the database randomly each time the application is started.
I am using CursorLoader and a ViewPager. I am having some buttons (put-to-favourites-button) on the screen, which updates a column in database using content resolver which triggers update() in ContentProvider. Update then causes a requery in order to update the change made. All standard.
This of course causes to give me rows that are ordered differently because of the order by random() clause, and causes the screen to "disappear".
Any idea how to get around this? I really want to keep the random ordering functionality.
class MyContentProvider extends ContentProvider {
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(resolver, uri);
return cursor;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = db.update(TABLE_NAME, values, selection, selectionArgs);
resolver.notifyChange(uri, null);
return count;
}
}
you can use something like
Cursor c = db.rawQuery("select * from table ORDER BY RANDOM()");
SQLite docs
What I ended up doing, as zapl suggested, was to add a column. that would hold myOrder, which would be populated with random values in onCreate, and then just do a Select with order by myOrder
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