ContentProvider getWriteableDatabase returns NullPointerException - android

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.

Related

OnLoadFinished suddenly not called anymore after update

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?

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.

How do I protect ContentResolver from being accessed by just any app?

Android newbie here.
I'm starting to learn about ContentProviders and I've set up my first ContentProvider which internally accesses a private SQLiteOpenHelper class to read and write data out of my database.
I take it one of the main benefits of ContentProviders is that you put all your data accessing code in the one place and the only time you're supposed to access the database is via ContentResolvers which use the ContentProvider's URI? [correct me if i'm wrong, i just figure that is the case as all the examples put SQLiteOpenHelper as a private class]
So I've recently written an update method in my ContentProvider which clears a column in my database. It looks roughly like this
#Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case FACT_ID:
String segment = uri.getPathSegments().get(1);
count = database.update(TABLE_FACT, values,
KEY_ID
+ "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
break;
case CLEAR_DATESEEN:
ContentValues cv = new ContentValues();
cv.putNull(KEY_DATESEEN);
count = database.update(TABLE_FACT, cv, null, null);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
The CLEAR_DATESEEN bit of the code is the one that clears the column.
This works but I was just wondering, doesn't this mean that any app on the device that calls that URI should be able to clear that column as well? What if I did not want other apps messing with my data?
Is there any way to prevent certain apps or only allow certain apps to be able to call my ContentProvider?
Unless you have set-up a special permission and provide that information to other apps (developers), your content provider is accessible only for your app. Please see Content Provider Permissions.

Android custom method in content provider to get number of records in table?

I have a content provider that accesses my database which is fine if you need to deal with record sets but I need a method to return an integer denoting the number of records in a table
The method looks like this
public long getRecordCount(String TableName) {
SQLiteDatabase mDatabase = mOpenHelper.getReadableDatabase();
String sql = "SELECT COUNT(*) FROM " + TableName;
SQLiteStatement statement = mDatabase.compileStatement(sql);
long count = statement.simpleQueryForLong();
return count;
}
But I am unable to find any way of using this (Or any other method that does not return a cursor for that matter) in a content provider so where is the best place to put this method and how to call it?
Obviously I could do the really bad option of selecting all the records with a managed query and using the cursor.count result but that is one hugely inefficient way of dealing with this specific requirement
Thanks
You can also simply use "count(*)" as a projection in a call to your content providers URIs, as in the following helper method
public static int count(Uri uri,String selection,String[] selectionArgs) {
Cursor cursor = getContentResolver().query(uri,new String[] {"count(*)"},
selection, selectionArgs, null);
if (cursor.getCount() == 0) {
cursor.close();
return 0;
} else {
cursor.moveToFirst();
int result = cursor.getInt(0);
cursor.close();
return result;
}
}
One way you can access it is by using the call() method in the ContentResolver class. I can't seem to find much about how to actually use this on google, but my guess is that you should just have your getRecordCount() return a bundle with your result in it. Of course the easier thing to do would be something like what's described in this SO Post.

Calling delete method in custom content provider

I am learning Android and I am stuck on an issue involving calling a custom content provider. I have been using an example in an instructional book and although it describes how to create the custom provider there is no clear example how to call the specific methods in it. I am specifically looking into how to delete a single record from the custom content provider.
Here is the code for the custom content provider (EarthquakeProvider.java):
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case QUAKES:
count = earthquakeDB.delete(EARTHQUAKE_TABLE, where, whereArgs);
break;
case QUAKE_ID:
String segment = uri.getPathSegments().get(1);
count = earthquakeDB.delete(EARTHQUAKE_TABLE, KEY_ID + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
I am trying to call the delete method from the main activity to delete a single entry, not the entire database. I want to use about an OnLongClickListener for the selected record that is displayed in a array list view in the main activity.
This is what I have come up with I have so far in my main activity for this method:
earthquakeListView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView _av, View _v, int _index,
long arg3) {
ContentResolver cr = getContentResolver();
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
return false;
}
I know the above code doesn't work, but this is as close as I could get with my current understanding.
Any help on this would be very much appreciated.
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
This is your problem. First, some context:
Content URIs: (source)
content://authority/path/##
The number at the end is optional. If present, the URI references a specific row in the database where row._id=(the number). If absent, it references the table as a whole.
the delete() call accepts a URI, a where clause, and a set of strings which get substituted in. Example: Say you have a database of people.
cr.delete(
Person.CONTENT_URI,
"sex=? AND eyecolor=?",
new String[]{"male", "blue"});
Will search the entire person table, and delete anyone whose sex is male and whose eye color is blue.
If the where clause and where values are null, then the delete() call will match every row in the table. This causes the behavior you see.
There are two methods to specify the row you want:
First option, you could append the number to the URI:
cr.delete(
EarthquakeProvider.CONTENT_URI.buildUpon().appendPath(String.valueOf(_id)).build(),
null, null);
This restricts the URI to a specific row, and the path will be through your case QUAKE_ID: statement and so will only delete one row no matter what.
Second option, you could use a where clause:
cr.delete(EarthquakeProvider.CONTENT_URI, "_id=?", String.valueOf(_id)));
Either way, you will restrict the delete to a single row, as you need it to. The latter makes for prettier code, but the former is more efficient, due to the way the ContentProvider and ContentObservers work.
As a last note: In your ContentProvider you need to add a call to
ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork). This helps notify cursors to re-fetch the database query and helps out a lot with automation.

Categories

Resources