OnLoadFinished suddenly not called anymore after update - android

I have a fragment with a list of items, each item has an editable quantity. When I edit the quantity, the database is updated and I receive a new cursor in the fragment. This all works well, except when you update the quantity a number of times, the onLoadFinished method is not called anymore. This can happen after 5 updates or after 200 updates: I can't reproduce the bug on command. The update is executed in the database because when I close and open that screen, I can see the updated quantity. Nothing out of the ordinary happens in the code: onLoaderReset is not called, notifyChange gets called on the correct Uri in the contentprovider. Has anyone ever seen this issue?
When the quantity needs to get updated:
ContentValues cv = new ContentValues();
cv.put(CartTable.COLUMN_COUNT, quantity);
String selection = CartTable.COLUMN_ID + " = " + cartItemId;
contentResolver.update(CartContentProvider.CONTENT_URI, cv, selection, null);
The CartContentProvider:
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
{
int rowsUpdated;
switch (uriMatcher.match(uri))
{
case CART_ITEMS:
rowsUpdated = this.database.update(CartTable.TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if(rowsUpdated > 0)
{
this.getContext().getContentResolver().notifyChange(CONTENT_URI, null);
this.getContext().getContentResolver().notifyChange(CONTENT_URI_PRODUCTS, null);
this.getContext().getContentResolver().notifyChange(CONTENT_URI_PRODUCTS_PHOTOS, null);
this.getContext().getContentResolver().notifyChange(CONTENT_URI_PHOTOS, null);
}
return rowsUpdated;
}
When I create the CursorLoader:
return new CursorLoader(this.getView().getContext(), CartContentProvider.CONTENT_URI_PRODUCTS, projection, null, null, null);
So for example, I edit the quantity 20 times and 20 times onLoadFinished gets called with a new cursor. The 21st time and all times after that, the update happens but onLoadFinished is not called anymore.
When extra code is required, let me know!
Thanks in advance.

Try using the CursorLoader like this:
return new CursorLoader(this.getActivity(), CartContentProvider.CONTENT_URI_PRODUCTS, projection, null, null, null);
use the fragment's Activity context, Also check where are you implementing the LoaderCallbacks interface, in the activity or in the fragment?

Related

SQLiteDatabase updates only when the application is reopened

I have an android app that successfully inserts data in my database when I click a marker in the map.
I have an activity that updates its info(column) in the database. It doesn't update on my first attempt but when I close, exit the app, run it again and update the info again, it successfully updates so I believe my code is right. What would the problem?
Process:
1st open
place marker
onInfoWindowClick starts new activityForResult
fill up fields
save (button)
return values to update a row
doesn't update
re-open
when an existing marker is clicked, fill up fields, it updates when saved
when a new marker is placed, fill up fields, doesn't update (means need to reopen again)
Call to update:
ContentValues cv = new ContentValues();
cv.put(LocationsDB.FIELD_TITLE, newReminder);
cv.put(LocationsDB.FIELD_MONTH, mm_final);
cv.put(LocationsDB.FIELD_DAY, dd_final);
cv.put(LocationsDB.FIELD_YEAR, yy_final);
getContentResolver().update(LocationsContentProvider.CONTENT_URI, cv, "lat=? AND lng=?", new String[] {location_lat, location_lng});
LocationContentProvider.java:
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs){
int count;
count = mLocationsDB.update(values, selection, selectionArgs);
return count;
}
LocationsDB.java:
public int update(ContentValues contentValues, String where, String[] whereArgs){
int cnt = mDB.update(DATABASE_TABLE, contentValues, where, whereArgs);
return cnt;
}
Most likely, your SQLite database has changed, but the ContentResolver does not know it. In your the update() method of your ContentProvider, add this line of code:
this.getContext().getContentResolver().notifyChange(uri, null);
This notifies the ContentResolver that a change has occurred. The resolver will then update an UI components that depend on the data.

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!

Fragments, ContentProviders and cursors on orientation change

Please help me understand what's happening here.
I have two fragments (A and B) in tabs reading data from different tables, via a CursorLoader and aContentProvider, in a Sqlite-database. With different URIs I can change which table the ContentProvider is quering.
I works as expected when switch between the tabs A and B, unless I switch to B, rotate and switches back to A the wrong cursor is returned. The cursor from fragment B is returned instead of a cursor for fragment A with makes the listView in fragment A to cause a crash. In some way the cursor seems to be reused on a rotation.
Why is this happening and how can I prevent that the wrong cursor is returned?
This is what I have in both fragment A and B. Tried to assing a loader id with no success.
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(mLoaderId, null, this);
}
My ContentProvider looks like this:
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case ALL_NEWS:
queryBuilder.setTables(NewsDb.SQLITE_TABLE);
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case SINGLE_PLACE:
queryBuilder.setTables(PlacesDb.SQLITE_TABLE);
String id = uri.getPathSegments().get(1);
queryBuilder.appendWhere(PlacesDb.KEY_ID + "=" + id);
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return cursor;
}
When using CursorLoader, data changes are automatically observed, so you should definitely remove the restartLoader call in your onResume(). If implemented properly, you should only have to call initLoader in onActivityCreated of the Fragment.
LoaderManager IDs are only scoped to the Fragment, so you should be using a static constant ID. If the Loaders are handled in the Fragments, themselves, it's perfectly fine for every Fragment to have the same Loader ID (even if they're managed by the same Activity).
So in each Fragment:
private static final int LOADER_ID = 0;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// ...
getLoaderManager().initLoader(LOADER_ID, null, this);
}

Cursorloader not refreshing when underlying data changes

I have a content provider, a content resolver, and a cursor loader. The loader is used to indirectly populate a listview (ie not a simple cursor adapter, rather an array adapter, since I need to use the cursor's results to gather other data).
When I change the underlying data, the listview does not re-populate as the onLoadFinished(Loader<Cursor>, Cursor) call back is not called.
As suggested while I'm writing this, there are a lot of questions on this issue.
eg
CursorLoader not updating after data change
And all the questions point out two things:
In your content provider query() method, you need to tell the cursor about notifications a'la
c.setNotificationUri(getContext().getContentResolver(), uri);
In your content provider insert/update/delete method, you need to notify on the URI:
getContext().getContentResolver().notifyChange(uri, null);
I'm doing those things.
I'm also NOT closing any of the cursors I get back.
Note, my content provider lives in a separate app (think of it as a content provider app -- no launcher main activity). A com.example.provider APK, and the com.example.app is calling (in the content resolver) via the content://com.example.provider/table URI etc. The content resolver (dbhelper) lives in a library project (com.example.appdb) that the activity links in. (This way, multiple projects can use the dbhelper via linking, and all content providers are installed via single APK)
I have turned on debugging in the loader manager, and can see where I force a refresh after the data changes (ie the loader being restarted and previous being marked inactive), but nothing that says anything is happening automatically -- rather, in response to my force refresh.
Any ideas why my loader isn't being refreshed ?
-- EDIT --
The loader create:
Log.d(TAG, "onCreateLoader ID: " + loaderID);
// typically a switch on the loader ID, and then
return dbHelper.getfoo()
Where getfoo() returns a cursor loader via:
return new CursorLoader(context, FOO_URI, foo_Fields, foo_query, foo_argArray, foo_sort );
Loader Finish takes the cursor and populates a table (and does some processing) -- nothing fancy there.
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
Log.d(TAG, "onLoadFinished id: " + loader.getId());
// switch on loader ID ..
FillTable(cursor, (FooAdapter) foo_info.listview.getAdapter();
Loader Reset clears the table.
public void onLoaderReset(Loader<Cursor> loader) {
Log.d(TAG, "onLoaderReset id: " + loader.getId());
// normally a switch on loader ID
((FooAdapter)foo_info.listview.getAdapter()).clear();
The content provider does:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor;
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (sUriMatcher.match(uri)) {
case FOO:
qb.setTables(FOO);
qb.setProjectionMap(FooProjectionMap);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
Then insert/update is similar:
public Uri insert(Uri uri, ContentValues initialValues) {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
String tableName;
Uri contentUri;
switch (sUriMatcher.match(uri)) {
case FOO:
tableName = FOOTABLE;
contentUri = FOO_URI;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
long rowId = db.insert(tableName, null, values);
if (rowId > 0) {
Uri objUri = ContentUris.withAppendedId(contentUri, rowId);
getContext().getContentResolver().notifyChange(objUri, null);
return objUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
I see that you aren't calling swapCursor or changeCursor in your onLoadFinished and onLoaderReset. You need to do that for your adapter to access the data loaded into the new cursor.
in onLoadFinished, call something like:
mAdapter.swapCursor(cursor)
In onLoaderReset, call this to remove references to the old cursor:
mAdapter.swapCursor(null)
Where mAdapter is your listView's adapter.
More info: http://developer.android.com/guide/components/loaders.html#onLoadFinished
So, for anyone who looks this up and has this problem:
Make certain that the URI you register the cursor on, and the URI you notify the cursor on are the same.
In my case, I was using a different URI to query than was used in other code that modified the underlying table. There wasn't an easy fix to make notification work, so I kept resetting the loader in OnResume() as the solution.
This is not a direct answer to this specific problem, but may help others in a similar situation. In my case, I had a join and even though I used the full tablename.columnname in the projection and query. It ended up using the wrong ID values anyway. So be sure to check your ContentProvider or SQL syntax.

ContentProvider getWriteableDatabase returns NullPointerException

I know this is a common problem, but nothing I've found solves my issue. I've created a ContentProvider, closely following the tutorial here. My Activity, in its onCreate method immediately after super.onCreate does this:
StitchProvider stitchProvider = new StitchProvider();
stitchProvider.delete(STITCHES_URI, null, null);
StitchProvider is my ContentProvider. I've followed this code through the debugger and depending on where I put breakpoints, one of two things happen, but both lead to a NullPointerException in LogCat. The first option is I put a breakpoint here:
public SQLData(Context c) {
super(c, DATABASENAME, null, DATABASEVERSION);
}
SQLData is my database class. If I put the breakpoint here, I see that c, the Context, is equal to android.app.Application#412a9b80. The code then returns to the onCreate method of StitchProvider:
public boolean onCreate() {
mDB = new SQLData(getContext());
return true;
}
and mDB becomes com.MyKnitCards.project.SQLData#412ab668. So far so good I think, but when I try to go past the return statement I get the NullPointerException. If I move the breakpoint to within the delete method of StitchProvider everything goes fine until I get to the getWriteableDatabase line, at which point I get a NullPointerException. Here's the delete method of StitchProvider:
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
int rowsAffected = 0;
switch (uriType)
{
case STITCHES:
SQLiteDatabase sqlDB = mDB.getWritableDatabase();
rowsAffected = sqlDB.delete(STITCHTABLE_BASEPATH, selection, selectionArgs);
break;
case STITCHES_ID:
SQLiteDatabase sqlDBwithID = mDB.getWritableDatabase();
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection))
{
rowsAffected = sqlDBwithID.delete(STITCHTABLE_BASEPATH, SQLData.KEY_ROWID + "=" + id, null);
}
else
{
rowsAffected = sqlDBwithID.delete(STITCHTABLE_BASEPATH, selection + " and " + SQLData.KEY_ROWID + "=" + id, selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown or Invalid URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsAffected;
}
One thing I have noticed, is that if I set a breakpoint at SQLData's onCreate method, I never get there. I think part of the problem is that the database is not getting created, but I don't know why. I'll post as much code or LogCat as people want, put I don't want to overwhelm folks with code either. Anyway, as always, if you have any suggestions, that'd be great, and thanks!
I see that you instantiate your provider in code, something that you shouldn't be doing as a ContentProvider is managed by the Android system. The correct way of using a ContentProvider is through a ContentResolver which can be obtain in an Activity with getContentResolver().
You can find more about ContentProviders and how to use them in the official tutorial on the android developers site.

Categories

Resources