App crashes when using Content Resolver and Content Provider - android

I have an activity class which uses the ContentResolver's query() method with Uri & projection[] being set as two of its arguments and rest are set as 'null'.
Like:
ContentResolver resolverCatalog = getContentResolver();
Cursor cursor = resolverCatalog.query(PetsEntry.CONTENT_URI,projection,null,null,null);
However in the ContentProvider class the query() method is defined as:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// Making an instance of the SQLiteOpenHelper class named as 'SQLdbHelper'
SQLdbHelper PetdbHelper = new SQLdbHelper(getContext());
//getting access to database
SQLiteDatabase database_query = PetdbHelper.getReadableDatabase();
// This cursor will hold the result of the query
Cursor cursor_query;
// Figure out if the URI matcher can match the URI to a specific code
int match = sUriMatcher.match(uri);
switch (match) {
case PETS:
cursor_query = database_query.query(TABLE_NAME,projection,null,null,null,null,null);
break;
case PET_ID:
selection = PetContract.PetsEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
// This will perform a query on the pets table where the _id equals 3 to return a
// Cursor containing that row of the table.
cursor_query = database_query.query(TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
return cursor_query;
}
The PETS & PETS_ID are defined (within this ContentProvider class) as:
public class PetProvider extends ContentProvider {
//object made of the helper class for the provider, to get access of the database
private SQLdbHelper PetdbHelper;
private static final int PETS = 1;
private static final int PET_ID = 2;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(CONTENT_AUTHORITY, PATH_PETS, PETS);
sUriMatcher.addURI(CONTENT_AUTHORITY, PATH_PETS_ID, PET_ID);
}...//class continues
In the AndroidManifest I've wrote:
<provider
android:name=".data.PetProvider"
android:authorities="com.example.android.petsretry.data"
android:exported="false">
</provider>
Have tried multiple things over it but couldn't get out of it...Please help!
Thanks in advance...

Have found the solution, problem was with the Android Manifest file, correct code is:
<provider android:name=".data.PetProvider" android:authorities="com.example.android.petsretry.data" android:exported="false"/>
All 'name', 'authorities' and 'exported' property should have been inside the opening tag of the provider instead of being in between the opening & closing tag of provider...
Hoof...

Related

Global variable defined in Content Provider and initialized in onCreate() is null in query()

I defined a content provider and made my database helper a global variable, which I initialize in the onCreate method. However, the value of this global variable is null, in the query() method.
This is my content provider:
/** Tag for the log messages */
public static final String LOG_TAG = FeedingProvider.class.getSimpleName();
// Declaring a FeedingDbHelper variable;
private FeedingDbHelper mDbHelper;
/** URI matcher code for the content URI for the pets table */
private static final int FEEDINGS = 100;
/** URI matcher code for the content URI for a single pet in the pets table */
private static final int FEEDING_ID = 101;
/**
* UriMatcher object to match a content URI to a corresponding code.
* The input passed into the constructor represents the code to return for the root URI.
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Static initializer. This is run the first time anything is called from this class.
static {
// The calls to addURI() go here, for all of the content URI patterns that the provider
// should recognize. All paths added to the UriMatcher have a corresponding code to return
// when a match is found.
sUriMatcher.addURI(FeedingContract.CONTENT_AUTHORITY, FeedingContract.PATH_FEEDINGS,
FEEDINGS);
sUriMatcher.addURI(FeedingContract.CONTENT_AUTHORITY, FeedingContract.PATH_FEEDINGS +
"/#", FEEDING_ID);
}
/**
* Initialize the provider and the database helper object.
*/
#Override
public boolean onCreate() {
mDbHelper = new FeedingDbHelper(this.getContext());
// Make sure the variable is a global variable, so it can be referenced from other
// ContentProvider methods.
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Log.d(LOG_TAG, "Test in query: " + (mDbHelper == null));
SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match) {
case FEEDINGS:
cursor = db.query(FeedingContract.FeedingEntry.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case FEEDING_ID:
selection = FeedingEntry._ID + "=?";
selectionArgs = new String[] {
String.valueOf(ContentUris.parseId(uri))
};
cursor = db.query(FeedingContract.FeedingEntry.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Can't find uri in PetProvider.query()");
}
Log.e("Gerke", "Cursor: " + DatabaseUtils.dumpCursorToString(cursor));
return cursor;
}
The resulting error message is:
"java.lang.NullPointerException: Attempt to invoke virtual method 'android.database.sqlite.SQLiteDatabase com.example.android.smartfeed.data.FeedingDbHelper.getReadableDatabase()' on a null object reference"
My questions is: why is the global variable mDbHelper null and not an instance of my dbHelper?
Also tried saving the context to a global variable, but this also does not work.
I found my mistake:
The definition of the ContentProvider is fine, but when using it, I tried creating a new instance of it, which is wrong.
So for everyone else having my problem:
The communication with the ContentProvider is done via the ContentResolver. To get an instance of the ContentResolver, call getContentResolver();

content provider custom client - no content is recieved

I have read many answers regarding content providers, but I can't understand why my client isn't recieving any data stored in the database.
-I'm using the same uri everywhere.
-I have added the read and write permissions in my provider in the manifest.
-I have exported the provider and set the multiprocess to "true" too.
-My client in app has the same read and write permissions.
-I created a jar containing the resolver for my client in app. I have added it as a library.
-I know that the parameters got into the database because I can see all the info inside the database.
-My client wasn't able to connect to the provider until I have done all mentioned above, because I fixed exception regarding it.
Now everything should work and doesn't crash any more, but all the strings I get from the resolver are empty. I don't know if it really conects to the provider or if something else is incorrect.
Please help me!
Q: Do the permission string and the authorities string in the provider declaration have to match the package structure?
Here are some pieces of code:
Provider declaration in manifest:
<provider
android:name=".lessons.LessonsProvider"
android:authorities="com.scheduler.lessons"
android:readPermission="com.lessons.READ_DATABASE"
android:writePermission="com.lessons.WRITE_DATABASE"
android:multiprocess="true"
android:exported="true" />
Resolver code:
public class LessonsResolver {
private static final String TAG = "LessonProvider";
private final String PROVIDER_NAME = "com.scheduler.lessons";
private Uri uri;
private Context mContext;
public LessonsResolver(Context context) {
super();
mContext = context;
uri = Uri.parse("content://"+ PROVIDER_NAME+"/parameters_table");
}
public String getString(String parameterName, String defaultValue) {
Cursor cursor = mContext.getContentResolver().query(uri, null, "parameter_name='"+parameterName+"'", null, null);
if (cursor == null || cursor.getCount() == 0) {
Log.e(TAG,"getString: for parameterName: " + parameterName + ", cursor from the content provider "+ (cursor == null ? "is NULL" : "is empty" ));
cursor.close();
return defaultValue;
}
cursor.moveToFirst();
String s = cursor.getString(2);
cursor.close();
return s;
}
Permissions in client app manifest:
<uses-permission android:name="com.lessons.READ_DATABASE"/>
<uses-permission android:name="com.lessons.WRITE_DATABASE"/>
My provider:
public class LessonsProvider extends ContentProvider {
public static final String PROVIDER_NAME = "com.scheduler.lessons";
private static final String TABLE_NAME = "lessons";
private static final String TAG = "LessonsProvider";
private static final Uri CONTENT_URI = Uri.parse("content://"+PROVIDER_NAME + "/parameters_table");
...
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(TABLE_NAME);
Cursor c = sqlBuilder.query(mSqlDB, projection, selection, selectionArgs, null, null, null);
return c;
}

Passing query variables to the ContentProvider in Android

I'm wanting to know what the correct way of passing parameters for a Database query using the ContentProvider pattern is.
Is it as follow - i.e. where & args in the update method.
getContentResolver().update(uri, cv,where, args);
Or is it by appending the values at the end of the URI - As in this example in the docs - see case 2?
public class ExampleProvider extends ContentProvider {
......
......
// Implements ContentProvider.query()
public Cursor query(
Uri uri, String[] projection,String selection, String[] selectionArgs,
String sortOrder) {
switch (sUriMatcher.match(uri)) {
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;
case 2:
selection = selection + "_ID = " uri.getLastPathSegment();
break;
And which of these approaches is the best. I've seen both used, but unsure of their respective merits.

How to match Uri's correctly in ContentProvider?

This is what I have, but its failing saying that the table does not exists. I am positive it does incase anyone asks that. But this is some of the code that does that:
What gets called
/*Constants*/
public static final String AUTHORITY = "content://com.smartcal.eventprovider";
private static final int EVENTS_INFO = 1;
baseUri = Uri.withAppendedPath(baseUri, "events_info");
return new CursorLoader(this, baseUri, args.getStringArray("projection"),
args.getString("selection"), args.getStringArray("selectionArgs"), args.getBoolean("sortOrder") ? args.getString("sortOrder") : null );
What matches is
private String getTable(Uri uri) {
String table = "";
switch(sUriMatcher.match(uri)){
case EVENTS_INFO: table = "events_info";
}
return table;
}
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sUriMatcher.addURI(AUTHORITY, "events_info", 1);
}
And my query() method in custom ContentProvider
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
database = openHelper.getWritableDatabase();
return database.query(getTable(uri), projection, selection, selectionArgs, null, null, null);
}
Error:
06-19 17:34:14.366: E/AndroidRuntime(686): Caused by:
java.lang.IllegalStateException: Invalid tables
EDIT:
When using Alex's IllegalArgumentException, this is what I got back from the error:
06-19 19:17:42.277: E/AndroidRuntime(1134): Caused by:
java.lang.IllegalArgumentException: Unknown URI
content://com.smartcal.eventprovider/events_info
As you can tell, it should match, assuming the sUriMatcher.addURI() method is working correctly.
Your problem might stem from the fact that your code isn't very well organized...
First thing,
private String getTable(Uri uri) {
switch(sUriMatcher.match(uri)) {
case EVENTS_INFO:
return "events_info"; // return
/** PROVIDE A DEFAULT CASE HERE **/
default:
// If the URI doesn't match any of the known patterns, throw an exception.
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
Second thing,
/** Use constants, not raw ints **/
sUriMatcher.addURI(AUTHORITY, "events_info", EVENT_INFO);
Other than that, you might want to provide some more info.
Have you already installed the app and is the table new? If so, and you are not updating the schema in your sql helper class, you need to uninstall the app first. Then install the app in your emulator device. The db is only created once, and the create method in the sql helper will not be called after that first time, even if you are installing or running a new build/apk.

Android ContentProvider URI scheme to notify CursorAdapters listening on OUTER JOIN queries

I have an Android ContentProvider which allows to do LEFT OUTER JOIN queries on a SQLite database.
Let's assume in the database I have 3 tables, Users, Articles and Comments. The ContentProvider is something like the following:
public class SampleContentProvider extends ContentProvider {
private static final UriMatcher sUriMatcher;
public static final String AUTHORITY = "com.sample.contentprovider";
private static final int USERS_TABLE = 1;
private static final int USERS_TABLE_ID = 2;
private static final int ARTICLES_TABLE = 3;
private static final int ARTICLES_TABLE_ID = 4;
private static final int COMMENTS_TABLE = 5;
private static final int COMMENTS_TABLE_ID = 6;
private static final int ARTICLES_USERS_JOIN_TABLE = 7;
private static final int COMMENTS_USERS_JOIN_TABLE = 8;
// [...] other ContentProvider methods
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String table = getTableName(uri);
// SQLiteWrapper is a wrapper class to manage a SQLiteHelper
Cursor c = SQLiteWrapper.get(getContext()).getHelper().getReadableDatabase()
.query(table, projection, selection, selectionArgs, null, null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
String table = getTableName(uri);
// SQLiteWrapper is a wrapper class to manage a SQLiteHelper
long id = SQLiteWrapper.get(getContext()).getHelper().getWritableDatabase()
.insert(table, null, values);
Uri itemUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(itemUri, null);
return itemUri;
}
private String getTableName(Uri uri) {
switch (sUriMatcher.match(uri)) {
case USERS_TABLE:
case USERS_TABLE_ID:
return "Users";
case ARTICLES_TABLE:
case ARTICLES_TABLE_ID:
return "Articles";
case COMMENTS_TABLE:
case COMMENTS_TABLE_ID:
return "Comments";
case ARTICLES_USERS_JOIN_TABLE:
return "Articles a LEFT OUTER JOIN Users u ON (u._id = a.user_id)";
case COMMENTS_USERS_JOIN_TABLE:
return "Comments c LEFT OUTER JOIN Users u ON (u._id = c.user_id)";
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "users", USERS_TABLE);
sUriMatcher.addURI(AUTHORITY, "articles", ARTICLES_TABLE);
sUriMatcher.addURI(AUTHORITY, "comments", COMMENTS_TABLE);
sUriMatcher.addURI(AUTHORITY, "users" + "/#", USERS_TABLE_ID);
sUriMatcher.addURI(AUTHORITY, "articles" + "/#", ARTICLES_TABLE_ID);
sUriMatcher.addURI(AUTHORITY, "comments" + "/#", COMMENTS_TABLE_ID);
sUriMatcher.addURI(AUTHORITY, "???", ARTICLES_USERS_JOIN_TABLE); // what uri here?
sUriMatcher.addURI(AUTHORITY, "???", COMMENTS_USERS_JOIN_TABLE); // what uri here?
}
}
What's the best URI scheme to notify all CursorAdapters listening on joined and non-joined queries every time I insert (or update) a row in the Users table?
In other words, if I add or update a new row in one of the tables, I want to send a single notification with getContext().getContentResolver().notifyChange(itemUri, null) so that all the CursorAdapters listening on any query (USERS_TABLE, ARTICLES_USERS_JOIN_TABLE, COMMENTS_USERS_JOIN_TABLE) receive a notification to update their content.
If this is not possible, is there an alternative way to notify all the observers?
You can have special Uri's to query with:
sUriMatcher.addURI(AUTHORITY, "articlesusers", ARTICLES_USERS_JOIN_TABLE);
sUriMatcher.addURI(AUTHORITY, "commentsusers", COMMENTS_USERS_JOIN_TABLE);
But I can't think of a way to send a single notification. It seems your best choice is to send a notification for each Uri that refers to the table being modified. So your insert/update/delete methods would call notifyChange multiple times depending on the table affected. For changes to "users" it would be 3 notifications--users, articlesusers and commentsusers--since they all depend on the "users" table.
As answered by prodaea, here is another alternative you can use for notification Uri. This is not a perfect solution, but it uses only one Uri for notification.
The solution is to use the main Uri without any table name (e.g:content://com.example.app.provider/) as the notification Uri in the query method for ARTICLES_USERS_JOIN_TABLE and COMMENTS_USERS_JOIN_TABLE. So, the related cursor will be notified whenever there is change in any table. There is one limitation though. That is, ARTICLES_USERS_JOIN_TABLE cursor will be notified even when there is change in Articles table.
For tables, Users' andArticles', you can use their specific Uris for notification.

Categories

Resources