Overriding `query()` in ContentProvider prevents Recent Suggestions From Showing - android

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;
}
}

Related

CursorLoader not updating after notifyChange call

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!

Android - Requerying cursor ordered by RANDOM()

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

CursorLoader not updating after data change

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;
}`

CursorLoader with rawQuery

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

How do I get the _count in my content provider?

What should I do to get my content provider to return the _count column with the count of records? The documentation says it is automatic, but maybe it's only taking about some built-in content provider. Running a query to the database seems not to return it.
If you are using contentProvider then you have to do it like count(*) AS count.
If you use cursor.getCount(), that would not be as efficient as the above approach. With cursor.getCount() you are fetching all the records just to get counts. The entire code should look like following -
Cursor countCursor = getContentResolver().query(CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
countCursor.moveToFirst();
int count = countCursor.getInt(0);
The reason why this works is because android needs a column name to be defined.
If you are using ContentProvider.query() a Cursor is returned. Call Cursor.getCount() to get a count of records in the returned cursor.
I had a similiar problem and found this worked for me. In the example below I wanted to get the count of images from the MediaStore provider.
final String[] imageCountProjection = new String[] {
"count(" + MediaStore.Images.ImageColumns._ID + ")",
};
Cursor countCursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageCountProjection,
null,
null,
null);
countCursor.moveToFirst();
int existingImageCount = countCursor.getInt(0);
With cursor.getCount() you can not assure that it returns the real number of items returned. There are much better ways:
1- If you are using Content Providers, you can do a query and use the Column (_COUNT) included in BaseColumns for your projection
#Override
public Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
projection = new String[] {
ContentContract.NotificationCursor.NotificationColumns._COUNT,
};
...
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
return cursor;
}
2- To do a rawQuery using SELECT COUNT(*) as #saurabh says in his response.

Categories

Resources