Am I misunderstanding something here? I'm trying to implement a ContentProvider in Android and for some reason the calling URI is not being matched.
In my ContentProvider I define the following:
private static final int GET_COURSES = 100;
public static final Uri COURSES_URI = Uri.withAppendedPath(CONTENT_URI, CourseTable.NAME);
private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
matcher.addURI(AUTHORITY, COURSES_URI.toString(), GET_COURSES);
}
Then, in my query call:
public Cursor query(Uri uri, ...)
{
int type = matcher.match(uri);
.
.
Here, type is always -1... In the debug window I've viewed both the passing in uri and COURSES_URI and the string representations are identical...
Any suggestions?
Thanks
Update:
I call the Content Provider using:
new CursorLoader(this, CoursesProvider.COURSES_URI, null, null, null, null);
... this is boggling my mind... just got uri.equals(COURSES_URI) == true, so something must be incorrect in the UriMatcher
Problem solved...
The initial problem was that COURSES_URI also contained the AUTHORITY path:
private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + DBManager.DB_NAME);
private static final Uri COURSES_URI = Uri.withAppendedPath(CONTENT_URI, CourseTable.NAME);
In the matcher.AddURI(authority,path,code) method the authority portion of path should be removed.
This can be obtained using COURSES_URI.getPath().substring(1) (substring to remove the leading '/' returned by getPath())
Related
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...
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;
}
I have a ContentProvider and I need to match some URIs containing UUIDs as wildcards.
UriMatcher from ContentProvider:
public static final Uri CONTENT_URI_NOTIFICATIONS = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH_NOTIFICATIONS);
public static final Uri CONTENT_URI_USERS = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH_USERS);
private static final int NOTIFICATIONS = 40;
private static final int USER_ID = 70;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sURIMatcher.addURI(AUTHORITY, BASE_PATH_NOTIFICATIONS, NOTIFICATIONS);
sURIMatcher.addURI(AUTHORITY, BASE_PATH_USERS + "/*", USER_ID);
}
Query Code for NOTIFICATIONS:
Uri uri = Uri.parse(String.valueOf(MyContentProvider.CONTENT_URI_NOTIFICATIONS));
return new CursorLoader(getActivity(), uri, projection, null, null, null);
Query Code for USER_ID:
String userId = "73279386-5459-4316-9ff9-7c6b7b84029a";
Uri uri = Uri.parse(MyContentProvider.CONTENT_URI_USERS + "/" + userId);
return new CursorLoader(getActivity(), uri, projection, null, null, null);
From the above UriMatcher, the NOTIFICATIONS URI matches, but the USER_ID does not. Any idea what could be wrong here ? Worth noting is that when I used to have integers instead of UUIDs for representing users and used to have # instead of * in the UriMatcher, everything worked as intended. After switching to the wildcard, the matcher stopped matching the URIs containing UUIDs.
Apparently the order in which you add the URIs counts. If you have set like below, USER_DETAILS won't be recognized anymore. You have to switch the order and add USER_DETAILS first.
sURIMatcher.addURI(AUTHORITY, BASE_PATH_USERS + "/*", USER_ID);
sURIMatcher.addURI(AUTHORITY, BASE_PATH_USERS + "/details", USER_DETAILS);
I am reading this tutorial on implementing my own ContentProvide for working with SQLite. Int the ContentProvider.query there are a few thing that puzzles me. It seems very hardcoded to just one table (the todo table in the tutorial), but maybe Im just not getting it? Now if I wanted to query another table, lets say nodo, how would I change the ContentProvider?
Should I append the table names somehow in queryBuilder.setTables(String inTables)?
What about the CONTENT_TYPE and CONTENT_ITEM_TYPE, should there be one for each table?
That about the TODO and TODO_ID varibles and the switch in the query method?
It seems I need to have a lot of if/switch conditions to support multiple tables with the same ContentProvider, is this the way to go or am I on a wrong path?
Thank you
Søren
Now if I wanted to query another table, lets say nodo, how would I change the ContentProvider?
Querying a new table would mean that you need to add a new Uri, since the Uri selects the datasource, similar to using a different table.
You would be adding essentially all the hardcoded values that are already there for the todos for your other table. For example:
// ------- usually the same for all
private static final String AUTHORITY = "de.vogella.android.todos.contentprovider";
// ------- define some Uris
private static final String PATH_TODOS = "todos";
private static final String PATH_REMINDERS = "reminders";
public static final Uri CONTENT_URI_TODOS = Uri.parse("content://" + AUTHORITY
+ "/" + PATH_TODOS);
public static final Uri CONTENT_URI_REMINDERS = Uri.parse("content://" + AUTHORITY
+ "/" + PATH_REMINDERS);
// ------- maybe also define CONTENT_TYPE for each
// ------- setup UriMatcher
private static final int TODOS = 10;
private static final int TODO_ID = 20;
private static final int REMINDERS = 30;
private static final int REMINDERS_ID = 40;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, PATH_TODOS, TODOS);
sURIMatcher.addURI(AUTHORITY, PATH_TODOS + "/#", TODO_ID);
sURIMatcher.addURI(AUTHORITY, PATH_REMINDERS, REMINDERS);
sURIMatcher.addURI(AUTHORITY, PATH_REMINDERS + "/#", REMINDERS_ID);
}
//#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TODO_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(TodoTable.COLUMN_ID + "="
+ uri.getLastPathSegment());
//$FALL-THROUGH$
case TODOS:
queryBuilder.setTables(TodoTable.TABLE_TODO);
break;
case REMINDERS_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(ReminderTable.COLUMN_ID + "="
+ uri.getLastPathSegment());
//$FALL-THROUGH$
case REMINDERS:
queryBuilder.setTables(ReminderTable.TABLE_REMINDER);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
Should I append the table names somehow in queryBuilder.setTables(String inTables)?
Yes, if different Uris read from different tables then set the table based on the Uri match.
What about the CONTENT_TYPE and CONTENT_ITEM_TYPE, should there be one for each table?
Depends on the actual content type. If they are different and you need a type yes. But you don't need to have them at all. That example defines them but doesn't even use them. It would need to return the type in getType, see documentation.
That about the TODO and TODO_ID varibles and the switch in the query method?
Those are constants defined for the UriMatcher which is explained nicely here. It's basically a simplification for String matching. A big ContentProvider can have 100 different Uris and selecting the right table in query would be painful if you would have to write if (uri.getPath().equals("todos") { /* code */ } else if (uri.. all the way.
Here's solution to your question, using UriMatcher, you can implement multiple tables in a content provider.
Content type and content item can be as follows and they can be wrapped in a separate class for each table
public static final String GENERAL_CONTENT_TYPE = "vnd.android.cursor.dir/vnd.myfirstapp.db.member" ;
public static final String SPECIFIC_CONTENT_TYPE = "vnd.android.cursor.item/vnd.myfirstapp.db.member" ;
`vnd.android.cursor.dir/vnd.yourownanything.anything.tablename'
this defines the general content type
`vnd.android.cursor.item/vnd.anthingasabove.table'
this also defines the specific and it is constant to any app those strings(words) vnd.android.cursor.dir and .item must be like that and after /vnd. must be like that
and in the class that extends contentprovider you just uset the same instance of UriMatcher to map the tables
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.