Accessing data from Custom Content Providers - android

I've two content provider based apps A and B. Both have their own content providers and are setup for reading data from A and B and vice versa. Everything works fine when the other app is in background. But couldn't find another content provider if the app is killed or not present in background. For example, App B wants to read data from App A. 'B' can read data from 'A' successfully when 'A' is running in background, but gave fatal error (Match uri not found) if 'A' is not running in background.
Any thoughts ?
[EDIT]
I'm getting the same issue as this post.
I've this in both apps' manifest:
<provider
android:name="MyContentProvider"
android:authorities="com.example.${applicationId}-provider"
android:enabled="true"
android:exported="true"
android:grantUriPermissions="true">
</provider>
This the error I'm getting:
Writing exception to parcel
java.lang.IllegalArgumentException: Unsupported URI(Query): content://com.example.appA-provider/appA
at com.example.provider.MyContentProvider.query(MyContentProvider.java:142)
at android.content.ContentProvider.query(ContentProvider.java:1007)
at android.content.ContentProvider$Transport.query(ContentProvider.java:218)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
at android.os.Binder.execTransact(Binder.java:461)
Note: This only happens when another app is not in background, otherwise it works as expected (can read each other's data fine).
[EDIT 2]
Here's code for MyContentProvider:
package com.example.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
public class MyContentProvider extends ContentProvider {
private static DatabaseHelper dbHelper;
private static final int ALL_ENTRIES = 1;
private static final int SINGLE_ENTRY = 2;
private String mAuthority = BuildConfig.APPLICATION_ID;
private static UriMatcher uriMatcher;
public Uri CONTENT_URI= null;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
}
public MyContentProvider() {}
public void init(String packageName, String authority) {
if (authority == null) {
setAuthority(packageName, true);
} else {
setAuthority(authority, false);
}
uriMatcher.addURI(getAuthority(), TABLE_NAME, ALL_ENTRIES);
uriMatcher.addURI(getAuthority(), TABLE_NAME + "/#", SINGLE_ENTRY);
CONTENT_URI =
Uri.parse("content://" + getAuthority() + "/" + TABLE_NAME);
}
private void setAuthority(String packageName, boolean isPackageName) {
if (isPackageName) {
mAuthority = packageName + ".myprovider";
} else {
mAuthority = packageName;
}
}
public String getAuthority() {
return mAuthority;
}
public Uri getContentUri() {
return CONTENT_URI;
}
#Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return false;
}
//Return the MIME type corresponding to a content URI
#Override
public String getType(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("Content uri is null: " + uri);
}
if (uriMatcher == null) {
throw new IllegalArgumentException("Unsupported Match URI: " + uri);
}
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
return "vnd.android.cursor.dir/vnd." + getAuthority() + "." + TABLE_NAME;
case SINGLE_ENTRY:
return "vnd.android.cursor.item/vnd." + getAuthority() + "." + TABLE_NAME;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
#Override
public Uri insert(Uri uri, ContentValues values) {
Uri _uri = null;
long id = 0;
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
case SINGLE_ENTRY:
id = db.insert(TABLE_NAME, null, values);
getContext().getContentResolver().notifyChange(uri, null);
_uri = Uri.parse(CONTENT_URI + "/" + id);
break;
default:
throw new IllegalArgumentException("Unsupported URI (insert): " + uri);
}
return _uri;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor cursor = null;
String id = null;
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
queryBuilder.setTables(TABLE_NAME);
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case SINGLE_ENTRY:
queryBuilder.setTables(TABLE_NAME);
id = uri.getPathSegments().get(1);
if (id != null && !id.isEmpty()) {
queryBuilder.appendWhere(TABLE_NAME + "=" + id);
}
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unsupported URI(Query): " + uri);
}
return cursor;
}
}

You can not initialize the content provider from somewhere else in your code like this, as the ContentProvider might be the first (or only) component of your app that's instantiated.
However, you can read the authority dynamically from the Manifest or a String resource. In my answer on Does Android Content Provider authority definition break the DRY rule? I outlined how we that in our OpenTasks-Provider.

I don't see a call to init() in content provider. Is it only called from elsewhere as part of the application's start ?
If so that may explains why the content provider fails when the application is not already started : In this case the UriMatcher is empty and the switch in the query() method falls back to default witch throws the IllegalArgumentException.
You should either call init() in onCreate() or fully initialize the UriMatcher in the static initializer.

You are setting the authority as
private void setAuthority(String packageName, boolean isPackageName) {
if (isPackageName) {
mAuthority = packageName + ".myprovider";
} else {
mAuthority = packageName;
}
}
So your mAuthority is either com.example.provider or com.example.provider.myprovider
However, you have defined authorities in the manifest as
android:authorities="com.example.${applicationId}-provider"
that is com.example.appA-provider

Related

Got Unknown URL content in Android

I am new to Android. I wanted to practice my knowledge about Content Provide, but my app somehow crush and tell me there is an unknown URL content. I try to search for solution but still can't fix the problem...
The logcat message:
2020-05-25 20:10:24.547 15120-15120/com.example.punchinandout E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.punchinandout, PID: 15120
java.lang.IllegalArgumentException: Unknown URL content://com.example.punchinandout.data/time
at android.content.ContentResolver.insert(ContentResolver.java:1535)
at com.example.punchinandout.MainActivity$1.onClick(MainActivity.java:65)
at android.view.View.performClick(View.java:6256)
at android.view.View$PerformClick.run(View.java:24701)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
My provider in Mainifest:
<provider
android:authorities="com.example.punchinandout"
android:name=".data.TimeProvider"
android:exported="false"/>
My Content Provider class:
package com.example.punchinandout.data;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import com.example.punchinandout.data.TimeContract.TimeEntry;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class TimeProvider extends ContentProvider {
private static final String TAG = TimeProvider.class.toString();
private static final int TIME = 100;
private static final int TIME_ID = 101;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//Determine which content URI we have.
static{
sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME);
sUriMatcher.addURI(TimeContract.CONTENT_AUTHORITY, TimeContract.CONTENT_PATH, TIME_ID);
}
private TimeHelper mDbHelper;
#Override
public boolean onCreate() {
mDbHelper = new TimeHelper(getContext());
return true;
}
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
SQLiteDatabase database = mDbHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
cursor = database.query(TimeEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query with unknown URI: " + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri); //Set notification URI on cursor, so that the cursor will update when the URI is updated.
return cursor;
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, #Nullable ContentValues values) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return insertTimeRecord(uri, values);
default:
throw new IllegalArgumentException("Insertion is no support for " + uri);
}
}
private Uri insertTimeRecord(Uri uri, ContentValues values){
//Sanity check
if (values.containsKey(TimeEntry.COLUMN_RECORD)){
String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
if (time_record == null){
throw new IllegalArgumentException("Time record cannot be empty.");
}
}
//If there are no values to update, then don't try to update the database.
if (values.size() == 0){
return null;
}
SQLiteDatabase database = mDbHelper.getWritableDatabase();
long newRowId = database.insert(TimeEntry.TABLE_NAME, null, values);
//Check if the insertion is success.
if (newRowId == -1){
Log.e(TAG, "Insertion fail with " + uri);
return null;
}
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
return ContentUris.withAppendedId(uri, newRowId);
}
#Override
public int update(#NonNull Uri uri, #Nullable ContentValues values, #Nullable String selection, #Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return updateTimeRecord(uri, values, selection, selectionArgs);
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
return updateTimeRecord(uri, values, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update fail with " + uri);
}
}
private int updateTimeRecord(Uri uri, ContentValues values, String selection, String[] selectionArgs){
//Sanity check
if (values.containsKey(TimeEntry.COLUMN_RECORD)){
String time_record = values.getAsString(TimeEntry.COLUMN_RECORD);
if (time_record == null){
return 0; //If the data is not inputted, update nothing in database.
}
}
//If there are no values to update, then don't try to update the database.
if (values.size() == 0){
return 0;
}
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int rowUpdated = database.update(TimeEntry.TABLE_NAME, values, selection, selectionArgs);
if (rowUpdated != 0){
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
}
return rowUpdated;
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, #Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return deleteTimeRecord(uri, selection, selectionArgs);
case TIME_ID:
selection = TimeEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
return deleteTimeRecord(uri, selection, selectionArgs);
default:
throw new IllegalArgumentException("Delete fail with " + uri);
}
}
private int deleteTimeRecord(Uri uri, String selection, String[] selectionArgs){
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int rowDelected = database.delete(TimeEntry.TABLE_NAME, selection, selectionArgs);
if (rowDelected != 0){
getContext().getContentResolver().notifyChange(uri, null); //Notify all listener the data has changed
}
return rowDelected;
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match){
case TIME:
return TimeEntry.CONTENT_LIST_TYPE;
case TIME_ID:
return TimeEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri + " with match " + match);
}
}
}
My Contract class:
package com.example.punchinandout.data;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.BaseColumns;
public class TimeContract {
private TimeContract(){};
public static final String CONTENT_AUTHORITY = "com.example.punchinandout.data";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String CONTENT_PATH = "time";
public static final class TimeEntry implements BaseColumns {
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
//MIME type of all records.
public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
//MIME type of single record.
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + CONTENT_PATH;
public static final String TABLE_NAME = "time";
public static final String _ID = BaseColumns._ID;
public static final String COLUMN_RECORD = "record";
}
}
Can someone help me? :(
I have fixed the problem!!
My provider in Mainifest is that:
<provider
android:authorities="com.example.punchinandout"
android:name=".data.TimeProvider"
android:exported="false"/>
And I change to :
<provider
android:authorities="com.example.punchinandout.data"
android:name=".data.TimeProvider"
android:exported="false"/>
Let the authorities be the same as the authority in Contract class and the problem is fixed!
Sorry for not placed the Mainifest in the question at first time as I had no idea where the problem in my app before.

Android DBFlow and CursorLoader

Anyone knows how to use cursorLoader with DBFlow ? I seen this issue but this is not added to DBFlow.
Thanks.
You can find official docs here or you can implement it the way i have
DBFlow ver used 3
//I have edited my answer & provided easier way for content provider part below
add this to manifest inside application
<provider
android:authorities="com.hashx19.pristinekashmir.mycontentprovider"
android:exported="false"
android:name=".MyContentProvider"/>
Create java file named MyContentProvider & copy below code in it
& replace AUTHORITY ,ENDPOINT, AppDatabase(Your database name) ,TableClassName as per you project.
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.net.Uri;
import com.hashx19.pristinekashmir.MySQLiteHelper;
import com.raizlabs.android.dbflow.annotation.ConflictAction;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.structure.ModelAdapter;
import java.util.Arrays;
import java.util.HashSet;
/**
* Created by Filu on 8/25/2016.
*/
public class MyContentProvider extends ContentProvider {
public static final String AUTHORITY = "com.hashx19.pristinekashmir.mycontentprovider";
private static final String ENDPOOINT = "feeds";
// #ContentUri(path = ENDPOOINT, type = ContentUri.ContentType.VND_MULTIPLE + ENDPOOINT)
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + ENDPOOINT);
private static final int feeds_CONTENT_URI = 0;
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
MATCHER.addURI(AUTHORITY, ENDPOOINT, feeds_CONTENT_URI);
}
;
#Override
public final String getType(Uri uri) {
String type = null;
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
type = "vnd.android.cursor.dir/" +ENDPOINT;
break;
}
default: {
throw new IllegalArgumentException("Unknown URI" + uri);
}
}
return type;
}
#Override
public boolean onCreate() {
return false;
}
#Override
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
android.database.Cursor cursor = null;
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
cursor = FlowManager.getDatabase("AppDatabase").getWritableDatabase().query("TableClassName", projection, selection, selectionArgs, null, null, sortOrder);
break;
}
}
if (cursor != null) {
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
#Override
public final Uri insert(Uri uri, ContentValues values) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName("AppDatabase", "TableClassName"));
final long id = FlowManager.getDatabase("AppDatabase").getWritableDatabase().insertWithOnConflict("TableClassName", null, values, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
}
default: {
throw new IllegalStateException("Unknown Uri" + uri);
}
}
}
#Override
public final int delete(Uri uri, String selection, String[] selectionArgs) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
long count = FlowManager.getDatabase("AppDatabase").getWritableDatabase().delete("TableClassName", selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return (int) count;
}
default: {
throw new IllegalArgumentException("Unknown URI" + uri);
}
}
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
switch(MATCHER.match(uri)) {
case feeds_CONTENT_URI: {
ModelAdapter adapter = FlowManager.getModelAdapter(FlowManager.getTableClassForName("AppDatabase", "TableClassName"));
long count = FlowManager.getDatabase("AppDatabase").getWritableDatabase().updateWithOnConflict("TableClassName", values, selection, selectionArgs, ConflictAction.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return (int) count;
}
default: {
throw new IllegalStateException("Unknown Uri" + uri);
}
}
}
}
then when overriding Loader methods do something like this
getLoaderManager().initLoader(1, null, this);
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String selection, sortOrder;
String[] selectionArgs, projection;
selection = ...;
selectionArgs = ...;
sortOrder = ...;
projection= new String[]{"id","date", "link","title","content","excerpt","author",};
CursorLoader cursorLoader = new CursorLoader(getContext(),MyContentProvider.CONTENT_URI, projection,null,null,null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
TableClass post = new TableClass();
while (!cursor.isAfterLast()) {
try{
post.setId(cursor.getInt(cursor.getColumnIndex("id")));
}catch (NullPointerException e){
e.printStackTrace();
}catch (CursorIndexOutOfBoundsException c){
c.printStackTrace();
}
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
editted
Figured out easier way to implement content provider .
add this to your manifest / or modify this way if you already have added Provider code .
modify your AppDatabase Class as
#ContentProvider(authority = AppDatabase.AUTHORITY,
database = AppDatabase.class,
baseContentUri = AppDatabase.BASE_CONTENT_URI)
#Database(name = AppDatabase.NAME, version = AppDatabase.VERSION)
public class AppDatabase {
public static final String NAME = "AppDatabase"; // we will add the .db extension
public static final int VERSION = 2;
public static final String AUTHORITY = "com.hashx19.pristinekashmir.dbflowcontentprovider";
public static final String BASE_CONTENT_URI = "content://"; }
modify each table you want to use as provider as
#TableEndpoint(name = PostData.ENDPOINT, contentProvider = AppDatabase.class)
#Table(database = AppDatabase.class ,allFields = true ,name = PostData.ENDPOINT)
public class PostData extends BaseModel {
public static final String ENDPOINT = "PostData";
#ContentUri(path = ENDPOINT, type = ContentUri.ContentType.VND_MULTIPLE + ENDPOINT)
public static final Uri CONTENT_URI = Uri.parse(AppDatabase.BASE_CONTENT_URI + AppDatabase.AUTHORITY
+ "/" + ENDPOINT);
#PrimaryKey
public int id;
public String image;
}
For using Content provider as in Cursor Loader use TableName.CONTENT_URI as in this case
CursorLoader cursorLoader = new CursorLoader(getContext(),PostData.CONTENT_URI,projection,null,null,null);

Content provider uris from one app to other access

I have created a content uri from one app :
content://SendDataProvider/bookevent
i am able to insert the values into it.
like :
content://SendDataProvider/bookevent/1
content://SendDataProvider/bookevent/2 etc...
but when i call it to other app for geting all record from this URI i use
String bookContentDataLog = "content://SendDataProvider/bookevent";
Uri bookrecord = Uri.parse(bookContentDataLog);
Cursor cursor = BookShelfActivity.this.getContentResolver().query(
bookrecord, null, null, null, null);
if (cursor.moveToFirst()) { // I got Null pointer exception here
String sessionId = cursor.getString(cursor.getColumnIndex(BOOK_COLUMN_SESSIONID));
String magId = cursor.getString(cursor.getColumnIndex(BOOK_COLUMN_MAG_ID));
}
I debug it and i found that
BookShelfActivity.this.getContentResolver().query(
bookrecord, null, null, null, null);
this return null value to cursor.
How i get all value inserted to this "content://SendDataProvider/bookevent"?
============== I have Used method to insert to uri ===================
1) Class Name and variables
public class SendDataProvider extends ContentProvider {
public SendDataProvider(Context c) {
this.context = c;//getContext();
BookDb dbHelper = new BookDb(context);
db = dbHelper.getWritableDatabase();
}
2) content provider class methode :===
#Override
public Uri insert(Uri uri, ContentValues contentValues) throws NullPointerException {
Log.e("SendDataProvider #Override inserted call","Uri insert");
long rowID = db.insert(BOOK_TABLE_NAME, null, contentValues);
Log.e("SendDataProvider #Override inserted ID","Uri insert ID"+rowID);
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
this.context.getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw new SQLException("Failed to add a record into " + uri);
}
3) query
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(BOOK_TABLE_NAME);
switch (uriMatcher.match(uri)) {
case uriCode:
qb.setProjectionMap(this.values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (sortOrder == null || sortOrder == "") {
sortOrder = BOOK_COLUMN_MAG_ID;
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null,
null, sortOrder);
c.setNotificationUri(this.context.getContentResolver(), uri);
return c;
}
SOLVED :
Step 1:
create Provider class with DB inser/update/delte/query and be carefull about this things.
static final String PROVIDER_NAME = "com.example.contentprovideruser.DataProvider";//"org.geometerplus.android.fbreader.interfaces.SendDataProvider";
static final String URL = "content://" + PROVIDER_NAME + "/bookevent";
static final Uri CONTENT_URI = Uri.parse(URL);
You need to create well URI
Step 2: Add PROVIDER_NAME AndroidManifest.xml
<provider android:name=".DataProvider"
android:authorities="com.example.contentprovideruser.DataProvider"
android:exported="true"
android:multiprocess="true">
</provider>
Step 3:Need to specify well MIME TYPE
#Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK:
return "vnd.android.cursor.dir/vnd.booktype";
case BOOK_ID:
return "vnd.android.cursor.item/vnd.booktype";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
step 4 :"CONTENT_URI" is your global uri where all value are inserted
you can use query to get result.
Uri students = DataProvider.CONTENT_URI;
Cursor c = getContentResolver().query(students, null, null, null, "name");
if (c.moveToFirst()) {
do{
Toast.makeText(this,
c.getString(c.getColumnIndex(DataProvider.BOOK_COLUMN_EVENT_NAME)) +
", " + c.getString(c.getColumnIndex( DataProvider.BOOK_COLUMN_FROMPAGE_NO)),
Toast.LENGTH_SHORT).show();
} while (c.moveToNext());
}
BE happy :)

Loader returns no data, or an empty cursor

I've got two loaders, each of which loads data from a different content provider.
The fragment is supplied with the ID of a medication from the first content provider, and the first loader loads all of the information related to that medication. The second loader is supposed to query the second content provider for all of the alarms associated with that medication.
The first loader works just fine, and returns all of the correct data. However, the second loader appears to return a null cursor, even though I know for a fact that there is plenty of data in the table that should be relevant. I say "appears" because using getCount() on the data in in onLoadFinished for the second loader causes my app to crash, and the only reason I can think that this would occur is if the cursor were null.
Anyway, here's the code for my loaders. If you need, I can give you the code for anything else you want.
/**
* Initializes the loaders.
*/
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
CursorLoader loader = null;
long id = getArguments().getLong(ARG_MED_ID);
switch(loaderId) {
case 0: // MedList Loader
Log.d("MedManager", "Loading med data");
Uri singleUri = ContentUris.withAppendedId(MedProvider.CONTENT_URI, id);
String[] projection = { MedTable.MED_ID,
MedTable.MED_NAME,
MedTable.MED_DOSAGE,
MedTable.MED_DATE_FILLED,
MedTable.MED_DURATION };
loader = new CursorLoader(getActivity(), singleUri,
projection, null, null,
MedTable.MED_NAME + " COLLATE LOCALIZED ASC");
break;
case 1: // AlarmList Loader
Log.d("MedManager", "Theoretically loading alarm list");
Uri baseUri = AlarmProvider.CONTENT_URI;
// Create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String[] alarmProjection = { DailyAlarmTable.ALARM_ID,
DailyAlarmTable.ALARM_MEDNUM,
DailyAlarmTable.ALARM_TIME };
String select = "((" + DailyAlarmTable.ALARM_MEDNUM + " NOTNULL) AND ("
+ DailyAlarmTable.ALARM_MEDNUM + " = " + id + "))";
loader = new CursorLoader(getActivity(), baseUri,
alarmProjection, select, null,
DailyAlarmTable.ALARM_TIMESTAMP + " ASC");
break;
}
return loader;
}
/**
* Customizes the various TextViews in the layout to match
* the values pulled from the MedTable, or swaps the alarm cursor
* into the adapter.
*/
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case 0:
setUpMedDetails(data);
break;
case 1:
Log.d("MedManager", "Alarm finished loading");
/*
* these lines are commented out because their presence causes
* the app to crash.
*/
/*
boolean isEmpty = data.getCount() < 1;
if(isEmpty) {
Log.d("MedManager", "No results");
}
*/
mAdapter.swapCursor(data);
break;
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
// TODO Auto-generated method stub
if(arg0.getId() == 1) {
mAdapter.swapCursor(null);
}
}
EDIT: For completeness' sake, and because the possibility always exists that I'm simply a massive idiot overlooking something obvious, here's the code by which I add alarms into the table:
/**
* This function will turn the hour and day into an "HH:mm AM/PM" string,
* calculate the timestamp, and then inserts them into the table.
*/
#Override
public void onTimePicked(int hourOfDay, int minute) {
Log.d("MedManager", "onTimePicked triggered");
// Convert the hour and minute into a string
String alarmString = formatAlarmString(hourOfDay, minute);
// Convert the hour and minute into a timestamp
long alarmTimestamp = getAlarmTimestamp(hourOfDay, minute);
// Define the URI to receive the results of the insertion
Uri newUri = null;
// Define a contentValues object to contain the new Values
ContentValues mValues = new ContentValues();
// Add medId;
long medId = getIntent().getLongExtra(MedDetailFragment.ARG_MED_ID, 0);
mValues.put(DailyAlarmTable.ALARM_MEDNUM, medId);
// Add the timestamp
mValues.put(DailyAlarmTable.ALARM_TIMESTAMP, alarmTimestamp);
// Add the time string
mValues.put(DailyAlarmTable.ALARM_TIME, alarmString);
// Insert the new alarm
Toast.makeText(getApplicationContext(), "medNum = " + medId, Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), "time = " + alarmString, Toast.LENGTH_SHORT).show();
newUri = getContentResolver().insert(AlarmProvider.CONTENT_URI, mValues);
String uriStr = newUri.toString();
Toast.makeText(getApplicationContext(), "Uri = " + uriStr, Toast.LENGTH_SHORT).show();
}
As requested, here's my AlarmProvider class.
package com.gmail.jfeingold35.medicationmanager.alarmprovider;
import java.util.Arrays;
import java.util.HashSet;
import com.gmail.jfeingold35.medicationmanager.database.AlarmDatabaseHelper;
import com.gmail.jfeingold35.medicationmanager.database.DailyAlarmTable;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class AlarmProvider extends ContentProvider {
// Database
private AlarmDatabaseHelper database;
// Used for the UriMatcher
private static final int ALARMS = 10;
private static final int ALARM_ID = 20;
private static final String AUTHORITY = "com.gmail.jfeingold35.medicationmanager.alarmprovider";
private static final String BASE_PATH = "medicationmanager";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/alarms";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/alarm";
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, BASE_PATH, ALARMS);
sUriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", ALARM_ID);
}
#Override
public boolean onCreate() {
database = new AlarmDatabaseHelper(getContext());
return false;
}
/**
* Perform a query from the alarm database
*/
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Using SQLiteQueryBuilder instead of the query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Check if the caller requested a column which doesn't exist
checkColumns(projection);
// Set the table
queryBuilder.setTables(DailyAlarmTable.TABLE_ALARM);
int uriType = sUriMatcher.match(uri);
switch(uriType) {
case ALARMS:
break;
case ALARM_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(DailyAlarmTable.ALARM_ID + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = database.getWritableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
// Make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return null;
}
/**
* Delete from the alarm database
*/
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
int rowsDeleted = 0;
switch(uriType) {
case ALARMS:
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM, selection,
selectionArgs);
break;
case ALARM_ID:
String id = uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)) {
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM,
DailyAlarmTable.ALARM_ID + "=" + id, null);
} else {
rowsDeleted = db.delete(DailyAlarmTable.TABLE_ALARM,
DailyAlarmTable.ALARM_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
long id = 0;
switch(uriType) {
case ALARMS:
id = db.insert(DailyAlarmTable.TABLE_ALARM, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sUriMatcher.match(uri);
SQLiteDatabase db = database.getWritableDatabase();
int rowsUpdated = 0;
switch(uriType) {
case ALARMS:
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
selection,
selectionArgs);
break;
case ALARM_ID:
String id = uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)) {
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
DailyAlarmTable.ALARM_ID + "=" + id,
null);
} else {
rowsUpdated = db.update(DailyAlarmTable.TABLE_ALARM,
values,
DailyAlarmTable.ALARM_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Confirms that the columns the user requested exist.
* #param projection
*/
public void checkColumns(String[] projection) {
String[] available = { DailyAlarmTable.ALARM_ID,
DailyAlarmTable.ALARM_MEDNUM,
DailyAlarmTable.ALARM_TIMESTAMP,
DailyAlarmTable.ALARM_TIME };
if(projection != null) {
HashSet<String> requestedColumns = new HashSet<String>(Arrays.asList(projection));
HashSet<String> availableColumns = new HashSet<String>(Arrays.asList(available));
// Check if all columns which are requested are available
if(!availableColumns.containsAll(requestedColumns)) {
throw new IllegalArgumentException("Unknown columsn in projection");
}
}
}
}
Oh oh, okay I found that you return null in your query method of AlarmProvider class :)). Let's return cursor for this
If the onLoadFinished() method passes you a null cursor, then that means that the ContentProvider's query() method has returned null. You need to fix your query() method so that it doesn't return null in this case.

Android: Refresh ListFragement using ContentProvider and Loader

I am using a SQLite database and a ContentProvider to fill a ListFragment. The problem is that ListFragment is not getting refreshed after I add a item. The ListFragment is empty. I have to close and reopen the app to show the added item in the list.
I try to update it like this:
public class RoomListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
//adapter using SQLite and ContentProvider to fill ListFragment
private SimpleCursorAdapter dataAdapter;
//needed for create room dialog
private EditText enter_room;
private static View textEntryView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//display ActionBar items
setHasOptionsMenu(true);
// TODO: replace with a real list adapter.
displayListView();
}
#Override
public void onResume() {
super.onResume();
//Starts a new or restarts an existing Loader in this manager
getLoaderManager().restartLoader(0, null, this);
}
#Override
public void onDestroy() {
super.onDestroy();
}
private void displayListView() {
// The desired columns to be bound
String[] columns = new String[] {
Database.KEY_GROUPADDRESS,
Database.KEY_NAME,
Database.KEY_DPT
};
// the XML defined views which the data will be bound to
int[] to = new int[] {
R.id.groupaddress,
R.id.name,
R.id.dpt,
};
// create an adapter from the SimpleCursorAdapter
dataAdapter = new SimpleCursorAdapter(
getActivity(),
R.layout.device_info,
null,
columns,
to,
0);
//set SimpleCursorAdapter to ListFragmentAdapter
setListAdapter(dataAdapter);
//Ensures a loader is initialized and active.
getLoaderManager().initLoader(0, null, this);
}
// This is called when a new Loader needs to be created.
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {
Database.KEY_ROWID,
Database.KEY_GROUPADDRESS,
Database.KEY_NAME,
Database.KEY_DPT};
CursorLoader cursorLoader = new CursorLoader(getActivity(),
MyContentProvider.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
dataAdapter.swapCursor(data);
dataAdapter.notifyDataSetChanged();
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
dataAdapter.swapCursor(null);
}
I add a item with this code:
//Handle OnClick events on ActionBar items
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle item selection
switch (item.getItemId()) {
case R.id.menu_add:
//Toast.makeText(getActivity(), "Click", Toast.LENGTH_SHORT).show();
LayoutInflater factory = LayoutInflater.from(getActivity());
//textEntryView is an Layout XML file containing text field to display in alert dialog
textEntryView = factory.inflate(R.layout.dialog_add_room, null);
//get the control from the layout
enter_room = (EditText) textEntryView.findViewById(R.id.enter_room);
//create Dialog
final AlertDialog.Builder alert1 = new AlertDialog.Builder(getActivity());
//configure dialog
alert1.setTitle("Raum hinzufügen:").setView(textEntryView)
.setPositiveButton("Hinzufügen",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
String roomname = enter_room.getText().toString();
Log.d("Insert: ", "Inserting ..");
ContentValues values = new ContentValues();
//TODO Richtige Spalte für Raumname verwenden
values.put(Database.KEY_NAME, roomname);
getActivity().getContentResolver().insert(MyContentProvider.CONTENT_URI, values);
dataAdapter.notifyDataSetChanged();
}
}).setNegativeButton("Abbrechen",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
//cancel dialog
}
});
alert1.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
My ContentProvider:
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class MyContentProvider extends ContentProvider{
private MyDatabaseHelper dbHelper;
private static final int ALL_COUNTRIES = 1;
private static final int SINGLE_COUNTRY = 2;
// authority is the symbolic name of your provider
// To avoid conflicts with other providers, you should use
// Internet domain ownership (in reverse) as the basis of your provider authority.
private static final String AUTHORITY = "de.mokkapps.fixknxdemo.contentprovider";
// create content URIs from the authority by appending path to database table
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/countries");
// a content URI pattern matches content URIs using wildcard characters:
// *: Matches a string of any valid characters of any length.
// #: Matches a string of numeric characters of any length.
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "countries", ALL_COUNTRIES);
uriMatcher.addURI(AUTHORITY, "countries/#", SINGLE_COUNTRY);
}
// system calls onCreate() when it starts up the provider.
#Override
public boolean onCreate() {
// get access to the database helper
dbHelper = new MyDatabaseHelper(getContext());
return false;
}
//Return the MIME type corresponding to a content URI
#Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case ALL_COUNTRIES:
return "vnd.android.cursor.dir/vnd.com.as400samplecode.contentprovider.countries";
case SINGLE_COUNTRY:
return "vnd.android.cursor.item/vnd.com.as400samplecode.contentprovider.countries";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
// The insert() method adds a new row to the appropriate table, using the values
// in the ContentValues argument. If a column name is not in the ContentValues argument,
// you may want to provide a default value for it either in your provider code or in
// your database schema.
#Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_COUNTRIES:
//do nothing
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
long id = db.insert(Database.SQLITE_TABLE, null, values);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(CONTENT_URI + "/" + id);
}
// The query() method must return a Cursor object, or if it fails,
// throw an Exception. If you are using an SQLite database as your data storage,
// you can simply return the Cursor returned by one of the query() methods of the
// SQLiteDatabase class. If the query does not match any rows, you should return a
// Cursor instance whose getCount() method returns 0. You should return null only
// if an internal error occurred during the query process.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Database.SQLITE_TABLE);
switch (uriMatcher.match(uri)) {
case ALL_COUNTRIES:
//do nothing
break;
case SINGLE_COUNTRY:
String id = uri.getPathSegments().get(1);
queryBuilder.appendWhere(Database.KEY_ROWID + "=" + id);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
return cursor;
}
// The delete() method deletes rows based on the selection or if an id is
// provided then it deleted a single row. The methods returns the numbers
// of records delete from the database. If you choose not to delete the data
// physically then just update a flag here.
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_COUNTRIES:
//do nothing
break;
case SINGLE_COUNTRY:
String id = uri.getPathSegments().get(1);
selection = Database.KEY_ROWID + "=" + id
+ (!TextUtils.isEmpty(selection) ?
" AND (" + selection + ')' : "");
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int deleteCount = db.delete(Database.SQLITE_TABLE, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return deleteCount;
}
// The update method() is same as delete() which updates multiple rows
// based on the selection or a single row if the row id is provided. The
// update method returns the number of updated rows.
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_COUNTRIES:
//do nothing
break;
case SINGLE_COUNTRY:
String id = uri.getPathSegments().get(1);
selection = Database.KEY_ROWID + "=" + id
+ (!TextUtils.isEmpty(selection) ?
" AND (" + selection + ')' : "");
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int updateCount = db.update(Database.SQLITE_TABLE, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
}
You should call notifyDataSetChanged(); from within your content providers' insert method and supply the URI as an argument.
At the point in time you are currently calling the notifyDataSetChanged(); method the insert may not have actually happened as the content provider call to insert will be handled asynchronously.
An example could look something like this
#Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase sqlDB = mDB.getWritableDatabase();
int uriType = sURIMatcher.match(uri);
long id;
switch (uriType) {
case TEAMS:
id = sqlDB.replace(TeamModel.TEAM_TABLE_NAME, null, values);
break;
case CARS:
id = sqlDB.replace(CarModel.CAR_TABLE_NAME, null, values);
break;
case TEAM_ERRORS:
id = sqlDB.replace(TeamErrorModel.TEAMS_ERRORS_TABLE_NAME, null, values);
String teamId = values.get(TeamErrorModel.COL_TEAM_ID).toString();
String selection = TeamModel.COL_ID + " = ?";
String[] selectionArgs = {teamId};
setErrorFlagTeamModel(sqlDB, true, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null, false);
return Uri.parse(uri + "/" + id);
}
The first argument is the URI passed in to the insert method and will tell ALL adapters listening in on that particular uri to update their data.
The last argument (false) tells a sync adapter to ignore this change. I assume you are not using a sync adapter
All methods in your ContentProvider should call the notifyChange method in a similar way.
You may well find that the insert actually failed. so check that the records are actually being inserted.
UPDATE
As per comment below from #zapi
And you need to add cursor.setNotificationUri(contentresolver, uri)
inside the query method or the Cursor does not know for which uri
notification it has to listen
Since answering your question you have posted your content provider and I can now see that in fact as per the above quote this is in fact your missing link

Categories

Resources