I have setup a fragment to pull data from a custom content provider using a CursorLoader.
The problem is that when i update a record in the SQLite table using the content resolver, the cursor does not refresh i.e. the getContext().getContentResolver().notifyChange(myUri, null) has no effect. I have to exit the fragment and open it again to see the change.
I think the problem is that the URI i have used to update a row is not being observed by the loader :
URI to create loader -content://com.myapp.provider/MyTable/Set/22
URI to update row -content://com.myapp.provider/MyTable/167
167 identifies a unique row in the table. 22 identifies a set of rows in the table. Is there some way to tell the loader that the row 167 comes within the set 22, so it should reset the cursor?
Here is the code in case it brings more clarity :
Creating CursorLoader in Fragment :
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle queryBundle) {
CursorLoader cursorLoader = new CursorLoader(getActivity(), Uri.parse("content://com.myapp.provider/MyTable/Set/22"), myProjection, null, null, null);
return cursorLoader;
}
on button click in fragment :
mContext.getContentResolver().update("content://com.myapp.provider/MyTable/167", values, null, null);
Content Provider class :
private static final String AUTHORITY = "com.myapp.provider";
private static final String TABLE_PATH = "MyTable";
public static final String CONTENT_URI_BASEPATH = "content://" + AUTHORITY + "/" + TABLE_PATH;
private static final int URITYPE_TABLE = 1;
private static final int URITYPE_SINGLE_SET = 2;
private static final int URITYPE_SINGLE_ROW = 3;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
sUriMatcher.addURI(AUTHORITY, TABLE_PATH,URITYPE_TABLE);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#", URITYPE_SINGLE_SET);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/#", URITYPE_SINGLE_ROW);
}
#Override
public int update(Uri myUri, ContentValues values, String selection, String[] selectionArgs){
int rowCount = 0;
String id;
SQLiteDatabase db = localDB.getWritableDatabase();
int uriType = sUriMatcher.match(myUri);
switch(uriType){
case URITYPE_SINGLE_ROW :
id = uri.getLastPathSegment();
//selection and selectionArgs are ignored since the URI itself identifies a unique row.
rowCount = db.update(MyTable.TABLE_NAME, values, MyTable.COLUMN_ID + " = ?", new String[] {id});
}
getContext().getContentResolver().notifyChange(myUri, null);
return rowCount;
}
I has a similar issue and found the solution here.
In brief, it turned out I needed to call setNotificationUri(ContentResolver cr, Uri uri) on the cursor returned by the query() method of my content provider.
The solution is to call notifyChange() on the Uri that is being observed i.e. the set and not on the row.
To achieve this, we need to make some changes :
Include the set ID in the URI when calling the update :
mContext.getContentResolver().update("content://com.myapp.provider/MyTable/Set/22/167", values, null, null);
Change the URI pattern of a single row from "/#" to "/Set/#/#"
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
sUriMatcher.addURI(AUTHORITY, TABLE_PATH,URITYPE_TABLE);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#", URITYPE_SINGLE_SET);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#/#", URITYPE_SINGLE_ROW);
}
Then in the update function, construct a new Uri that has to be notified :
List<String> pathSegments = uri.getPathSegments();
String mySetID = pathSegments.get(2);
Uri mySetUri = Uri.parse("content://" + AUTHORITY + "/" + TABLE_PATH + "/Set/" + mySetID);
getContext().getContentResolver().notifyChange(mySetUri, null);
Related
I have the next Content Provider
To insert a row in Table AGENDA, I do:
ContentValues values = new ContentValues(1);
values.put("MSG", "test");
context.getContentResolver().insert(DataProvider.CONTENT_URI_AGENDA, values);
and all works well.
But now, I´d like to use the uri with AGENDA_INSERTWITHCONFLICT to insert a row.
Please, How can I modify the line :
context.getContentResolver().insert(DataProvider.CONTENT_URI_AGENDA, values);
to do it?
Here is my Provider:
public class DataProvider extends ContentProvider {
public static final String TAGPROVIDER = "net.techabout.medappointment.provider";
public static final Uri CONTENT_URI_AGENDA = Uri.parse("content://"+TAGPROVIDER+"/agenda");
public static final String TABLE_AGENDA = "agenda";
private DbHelper dbHelper;
private static final int AGENDA_ALLROWS = 5;
private static final int AGENDA_INSERTWITHCONFLICT=7;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(TAGPROVIDER, "agenda", AGENDA_ALLROWS);
uriMatcher.addURI(TAGPROVIDER, "agenda", AGENDA_INSERTWITHCONFLICT);
}
#Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long id;
switch (uriMatcher.match(uri)) {
case AGENDA_ALLROWS:
id = db.insertOrThrow(TABLE_AGENDA, null, values);
break;
case AGENDA_INSERTWITHCONFLICT:
id=db.insertWithOnConflict(TABLE_AGENDA, BaseColumns._ID, values, SQLiteDatabase.CONFLICT_REPLACE);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
Uri insertUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
}
}
make following changes, Please use naming convetions as required.
// content provider
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(TAGPROVIDER, "agenda", AGENDA_ALLROWS);
uriMatcher.addURI(TAGPROVIDER, "agenda_insert_conflicts", AGENDA_INSERTWITHCONFLICT);
}
calling mechanism
String URL = "net.techabout.medappointment.provider/agenda_insert_conflicts";
Uri uri = Uri.parse(URL);
context.getContentResolver().insert(uri , values);
I'm trying to add prepopulated SQLITE tables to my content provider using SQLiteAssetHelper but the uri matcher won't match. I can access/modify the tables through standard SQL but using a cursor loader throws an exception. Here is the relevant code in the content provider/cursor loader.
//Content provider code
private PantryDbHelper dbHelper;
private static final int PANTRY = 1;
private static final int INFO = 5;
public static final String AUTHORITY = "com.battlestarmathematica.stayfresh.pantryprovider";
//path to db
public static final String URL = "content://" + AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse(URL);
public static final Uri CONTENT_URI_PANTRY = Uri.withAppendedPath(CONTENT_URI,"pantry");
public static final Uri CONTENT_URI_INFO = Uri.withAppendedPath(CONTENT_URI,"info");
static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"info",INFO);
uriMatcher.addURI(AUTHORITY, "pantry", PANTRY);
}
public Cursor query(#NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
SQLiteDatabase db = dbHelper.getReadableDatabase();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
switch (uriMatcher.match(uri)) {
case PANTRY:
builder.setTables(PantryContract.PANTRY_TABLE_NAME);
break;
case INFO:
builder.setTables("info");
default:
throw new IllegalArgumentException("Unsupported URI " + uri);
}
//Cursor loader code
public Loader<Cursor> onCreateLoader(int id, Bundle args){
return new CursorLoader(
//context
this,
//content URI
PantryContentProvider.CONTENT_URI_INFO,
//columns to return
new String[] {"_id","itemname"},
//selection
null,
//selection args
null,
//sort order
"itemname");
}
I know the cursor loader works because I use the exact same code for another activity with the pantry uri and it works perfectly. When I try to load it using the info uri though I get this exception.
java.lang.IllegalArgumentException: Unsupported URI content://com.battlestarmathematica.stayfresh.pantryprovider/info
Any help would be greatly appreciated.
You're just missing a break statement in your code.
The UriMatcher matches and the switch statement jumps to case INFO:, but since there is no break; the default: case is executed as well.
Try to replace this
case INFO:
builder.setTables("info");
default:
throw new IllegalArgumentException("Unsupported URI " + uri);
by this:
case INFO:
builder.setTables("info");
break;
default:
throw new IllegalArgumentException("Unsupported URI " + uri);
My android app is supposed to load all contacts recorded in the device and display them in a list. I haven't been able to figure out why, but not all contacts are being loaded.
Here is the code I'm using to start the query with a CursorLoader:
public android.support.v4.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == ContactsQuery.QUERY_ID) {
Uri contentUri;
if (mSearchTerm == null) {
contentUri = ContactsQuery.CONTENT_URI;
} else {
contentUri =
Uri.withAppendedPath(ContactsQuery.FILTER_URI, Uri.encode(mSearchTerm));
}
return new CursorLoader(getActivity(),
contentUri,
ContactsQuery.PROJECTION,
ContactsQuery.SELECTION,
null,
ContactsQuery.SORT_ORDER);
ContactsQuery is defined as follows:
public interface ContactsQuery {
final static int QUERY_ID = 1;
final static Uri CONTENT_URI = Contacts.CONTENT_URI;
final static Uri FILTER_URI = Contacts.CONTENT_FILTER_URI;
#SuppressLint("InlinedApi")
final static String SELECTION =
(Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
"<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
#SuppressLint("InlinedApi")
final static String SORT_ORDER =
Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;
#SuppressLint("InlinedApi")
final static String[] PROJECTION = {
// The contact's row id
Contacts._ID,
Contacts.LOOKUP_KEY,
Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID,
SORT_ORDER,
};
final static int ID = 0;
final static int LOOKUP_KEY = 1;
final static int DISPLAY_NAME = 2;
final static int PHOTO_THUMBNAIL_DATA = 3;
final static int SORT_KEY = 4;
}
Why aren't all contacts being loaded when mSearchTerm is null?
You are only pulling down contents of the phone's contacts - the contacts that are stored on the SIM may or may not be accessible due to the phone's settings (for example if the phone has disabled access to SIM contacts).
Here is a post that will show you how to read them separately.
You can potentially discern between the two using the following:
//for SIM Card
ContentValues values = new ContentValues();
values.put(RawContacts.ACCOUNT_TYPE, "com.android.contacts.sim");
values.put(RawContacts.ACCOUNT_NAME, "SIM");
Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);
//for everyone else
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
values.put(StructuredName.DISPLAY_NAME, "Name");
The problem was in the selection. The offending line is
final static String SELECTION =
(Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
"<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
The missing contacts had Contacts.IN_VISIBLE_GROUP <> 1. This behavior seems a bit pathological because it seems that perfectly valid contacts that are displayed in the contact list for the built in apps, such as my mother, are not in the visible group. I implemented a kludge:
final static String SELECTION =
(Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
"<>'' AND ("+Contacts.HAS_PHONE_NUMBER+"=1 OR "+Contacts.IN_VISIBLE_GROUP+" = 1)";
This is sufficient for my app.
I have created an app in which I use a ContentProvider I created to keep track of some photos my app creates.
The concept is simple.
I capture some Images and save the Image name, date, and path to a ContentProvider.
And then I use this content provider to load each time thumbnails etc from this ContentProvider when my app starts etc.
I use a CursorAdapter and ListActivity for the full picture.
If the user deletes the image from a file manager or through my app, I want to delete that entry from the ContentProvider.
I have this method which is supposed to do so.
private void deletePhotoFromContentProvider(String path){
String[] selectionArgs=new String[]{path};
Log.i(TAG, "Path to delete:" + path);
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=?";
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, selectionArgs);
Log.i(TAG, "Deleted :" + Integer.toString(res));
mContext.getContentResolver().notifyChange(PhotosContract.CONTENT_URI, null);
}
My table has an ID, a photo_name, photo_date, photo_file_path.
I perform the delete based on the file_path which is basically unique.
I checked the path and the Log prints the correct one, but when i try to delete this deletes all my entries. Note that the
ContentValues values = initValues();
mContext.getContentResolver().insert(PhotosContract.CONTENT_URI, values);
works properly.
This is also my PhotosContract class
public class PhotosContract {
public static final String AUTHORITY = "com.android.testapp.provider";
public static final Uri BASE_URI = Uri
.parse("content://" + AUTHORITY + "/");
public static final String PHOTOS_TABLE_NAME = "photos";
// The URI for this table.
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_URI,
PHOTOS_TABLE_NAME);
public static final String _ID = "_id";
public static final String PHOTO_FILE_PATH = "photo_file_path";
public static final String PHOTO_FILE_NAME = "photo_name";
public static final String PHOTO_DATE = "photo_date";
}
I also tried to use something like this:
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=" + path;
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, null);
but does not work either. It also deletes all my ContentProvider entries.
Any help?
Thanks in advance.
EDIT:
I am posting my delete method from my ContentProvider:
#Override
public int delete(Uri arg0, String arg1, String[] arg2) {
int rowsDeleted = mDbHelper.getWritableDatabase().delete(
PhotosContract.PHOTOS_TABLE_NAME, arg1, arg2);
getContext().getContentResolver().notifyChange(
PhotosContract.CONTENT_URI, null);
return rowsDeleted;
}
This was working! I'm not sure what changed. I do use git and I have looked over my commits and the code for hours now.
As the title indicates my uri matcher stopped matching.
Below I have pasted the relevant parts of my content provider.
public class Provider extends ContentProvider {
private static final String TAG = "Provider";
private static final String SCHEME = ContentResolver.SCHEME_CONTENT;
private static final String AUTHORITY = "com.snot.bodyweightworkout.database.provider";
private static final String BASE_URI = SCHEME + AUTHORITY;
public static final Uri URI_EXERCISES = Uri.parse(BASE_URI + "/exercise");
public static final Uri URI_PROGRAMS = Uri.parse(BASE_URI + "/program");
public static final Uri URI_PROGRAM_EXERCISES = Uri.parse(BASE_URI + "/program/#/exercise");
private static final int EXERCISE = 1;
private static final int EXERCISES = 2;
private static final int PROGRAM = 3;
private static final int PROGRAMS = 4;
private static final int PROGRAM_EXERCISES = 5;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sURIMatcher.addURI(AUTHORITY, "/exercise", EXERCISES);
sURIMatcher.addURI(AUTHORITY, "/exercise/#", EXERCISE);
sURIMatcher.addURI(AUTHORITY, "/program", PROGRAMS);
sURIMatcher.addURI(AUTHORITY, "/program/#", PROGRAM);
sURIMatcher.addURI(AUTHORITY, "/program/#/exercise", PROGRAM_EXERCISES);
}
...
And then the part where the actual matching should take place.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.v(TAG, "URI: " + uri);
Cursor result = null;
int match = sURIMatcher.match(uri);
switch(match)
{
case PROGRAMS:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Program.TABLE_NAME, Program.FIELDS, null, null, null, null, null, null);
result.setNotificationUri(getContext().getContentResolver(), URI_PROGRAMS);
break;
case PROGRAM:
final long pid = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Program.TABLE_NAME, Program.FIELDS,
Program.COL_ID + " IS ?",
new String[] { String.valueOf(pid) }, null, null, null, null);
result.setNotificationUri(getContext().getContentResolver(), URI_PROGRAMS);
break;
...
default:
throw new UnsupportedOperationException("Unmatched(" + match + ") URI: " + uri.toString());
I'm trying to query using a cursor loader like this:
getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), Provider.URI_PROGRAMS, Program.FIELDS, null, null, null);
}
Everytime default is hit. Not match is made the I end up with a FC and the following line in my log.
E/AndroidRuntime( 1979): Caused by: java.lang.UnsupportedOperationException: Unmatched(-1) URI: content://com.snot.bodyweightworkout.database.provider/program
I've been starring at this for hours and I really need some fresh eyes to take a peak at it. So if some kind soul could take a look at it I will appreciate it very much. Thanks in advance.
There's no need for the starting /, so define your addUri() method calls like this:
static {
sURIMatcher.addURI(AUTHORITY, "exercise", EXERCISES);
sURIMatcher.addURI(AUTHORITY, "exercise/#", EXERCISE);
sURIMatcher.addURI(AUTHORITY, "program", PROGRAMS);
sURIMatcher.addURI(AUTHORITY, "program/#", PROGRAM);
sURIMatcher.addURI(AUTHORITY, "program/#/exercise", PROGRAM_EXERCISES);
}