I am trying to have a contentprovider , but I am having trouble because I am getting an error of "Failed to find provider info for com.example.alex.hopefulyworks"
Here is the manifest and the contentprovider
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.alex.hopefulythisworks" >
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:name=".ColorContentProvider"
android:authorities="com.example.alex.hopefulythisworks.ColorContentProvider"
android:enabled="true"
android:exported="true" >
</provider>
</application>
</manifest>
package com.example.alex.hopefulythisworks;
....
public class ColorContentProvider extends ContentProvider {
private ColorHelper database;
private static final String AUTHORITY
= "com.example.alex.hopefulythisworks";
private static final String BASE_PATH
= "tasks";
public static final Uri CONTENT_URI
= Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
public static final String CONTENT_URI_PREFIX
= "content://" + AUTHORITY + "/" + BASE_PATH + "/";
private static final UriMatcher sURIMatcher =
new UriMatcher(UriMatcher.NO_MATCH);
private static final int COLOR = 1;
private static final int COLOR_ROW = 2;
static {
sURIMatcher.addURI(AUTHORITY, BASE_PATH, COLOR);
sURIMatcher.addURI(AUTHORITY, BASE_PATH + "/#", COLOR_ROW);
}
#Override
public boolean onCreate() {
database = new ColorHelper(getContext());
Log.d("contentprovider" , "inside content provider oncreate ");
return false;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
long id = 0;
switch (uriType) {
case COLOR:
id = sqlDB.insert(ColorTable.TABLE_TASK,
null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: "
+ uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse( CONTENT_URI_PREFIX + id);
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// check if the caller has requested a column which does not exists
ColorTable.validateProjection( projection );
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables( ColorTable.TABLE_TASK );
switch ( sURIMatcher.match(uri) ) {
case COLOR:
break;
case COLOR_ROW:
// add the task ID to the original query
queryBuilder.appendWhere( ColorTable.COLUMN_ID + "=" + uri.getLastPathSegment() );
break;
default:
throw new IllegalArgumentException("Invalid URI: " + uri);
}
System.out.println("before null check");
if(database == null){
System.out.println("database is null");
database = new ColorHelper(getContext());
}
System.out.println("after null check");
SQLiteDatabase db = database.getWritableDatabase();
Cursor cursor = queryBuilder.query( db, projection, selection,
selectionArgs, null, null, sortOrder);
// notify listeners
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
switch ( sURIMatcher.match(uri) ) {
case COLOR:
rowsDeleted = sqlDB.delete(ColorTable.TABLE_TASK, selection, selectionArgs);
break;
case COLOR_ROW:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete( ColorTable.TABLE_TASK,
ColorTable.COLUMN_ID + "=" + id,
null);
} else {
rowsDeleted = sqlDB.delete( ColorTable.TABLE_TASK,
ColorTable.COLUMN_ID + "=" + id
+ " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Invalid URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection , String[] selectionArgs) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
switch ( sURIMatcher.match(uri) ) {
case COLOR:
rowsUpdated = sqlDB.update( ColorTable.TABLE_TASK,
values,
selection,
selectionArgs);
break;
case COLOR_ROW:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update( ColorTable.TABLE_TASK,
values,
ColorTable .COLUMN_ID + "=" + id,
null );
} else {
rowsUpdated = sqlDB.update( ColorTable.TABLE_TASK,
values,
ColorTable.COLUMN_ID + "=" + id
+ " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Invalid URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
}
authorities need to have only the package name, and name is the complete package and class name.
<provider android:name="com.example.alex.hopefulythisworks.ColorContentProvider"
android:authorities="com.example.alex.hopefulythisworks"
android:enabled="true"
android:exported="true" >
</provider>
Remember that this needs to match the same that you're using to build the Uri:
private static final String AUTHORITY = "com.example.alex.hopefulythisworks";
Android docs
android:authorities="com.example.alex.hopefulythisworks.ColorContentProvider"
should be the same as
private static final String AUTHORITY
= "com.example.alex.hopefulythisworks";
so another solution is replace AUTHORITY to
private static final String AUTHORITY
= "com.example.alex.hopefulythisworks.ColorContentProvider";
Hope it helps.
Just make sure you're using fully qualified name as authority in your searchable as well in provider declared in manifest file like below:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="#string/action_search"
android:label="#string/app_name"
android:searchSuggestAuthority="your_pakcage_name.SearchSuggestionProvider"
android:searchSuggestSelection=" ?" />
<provider
android:authorities="your_pakcage_name.SearchSuggestionProvider"
android:name="your_pakcage_name.SearchSuggestionProvider"
android:exported="true"
android:enabled="true"
android:multiprocess="true"/>
If you've sensitive information in your providers make sure exported is not true
<provider
android:name="com.example.absolutelysaurabh.todoapp.ColorContentProvider"
android:authorities="com.example.absolutelysaurabh.todoapp"
android:enabled="true"
android:exported="false" >
</provider>
IMPORTANT : Make sure in the "Contract" class u've also used the same AUTHORITY as used here i.e. "com.example.absolutelysaurabh.todoapp"
(SOLVED)
I got this issue with my AddStickerPackActivity.java in function createIntentToAddStickerPack
where I commented intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_AUTHORITY, BuildConfig.CONTENT_PROVIDER_AUTHORITY); after watching some tutorials
Please check if you also find the same issue
Uncomment this line and make sure you are not importing any BuildConfig library, is it not required to import any BuildConfig
This might be old, the given answer works fine with APIs before API 30...
But for API 30 and API 31 checkout this:
Failed to find Content Provider in API 30
Related
I'm creating a simple ContentProvider to access the data in my SQLite3 database. Although I've declared the provider in my AndroidManifest.xml file I'm getting "Failed to find provider info for
com.tur_cirdictionary.turkishcircassiandictionary".
What is the problem here?
Here is my AndroidManifest.xml and ContentProvider files.
This is the Github link for the project if needed.
//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tur_cirdictionary.turkish_circassiandictionary">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".ArchiveActivity"></activity>
<activity android:name=".Searchable">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="#xml/searchable" />
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value=".Searchable" />
</activity>
<provider
android:name="com.tur_cirdictionary.turkish_circassiandictionary.data.WordProvider"
android:authorities=
"com.tur_cirdictionary.turkish_circassiandictionary"
android:exported="false" />
</application>
//WordProvider.java
package com.tur_cirdictionary.turkish_circassiandictionary.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.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import static com.tur_cirdictionary.turkish_circassiandictionary.data.WordContract.BASE_CONTENT_URI;
import static com.tur_cirdictionary.turkish_circassiandictionary.data.WordContract.WordEntry;
public class WordProvider extends ContentProvider {
public static final String LOG_TAG = WordProvider.class.getSimpleName();
private static final int WORDS = 100;
private static final int WORD_ID = 101;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(WordContract.CONTENT_AUTHORITY, WordContract.PATH_WORDS, WORDS);
sUriMatcher.addURI(WordContract.CONTENT_AUTHORITY, WordContract.PATH_WORDS
+ "/#", WORD_ID);
}
private WordDbHelper wordDbHelper;
#Override
public boolean onCreate() {
wordDbHelper = new WordDbHelper(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 = wordDbHelper.getReadableDatabase();
Cursor cursor;
int match = sUriMatcher.match(uri);
switch (match) {
case WORDS:
cursor = database.query(WordEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
case WORD_ID:
selection = WordEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
cursor = database.query(WordEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
return cursor;
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match) {
case WORDS:
return WordEntry.CONTENT_LIST_TYPE;
case WORD_ID:
return WordEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalStateException("Unknown URI " + uri + " with match" + match);
}
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, #Nullable ContentValues values) {
int match = sUriMatcher.match(uri);
switch (match) {
case WORDS:
return insertWord(uri, values);
default:
throw new IllegalArgumentException("Insertion is not supported for " + uri);
}
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection,
#Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
SQLiteDatabase database = wordDbHelper.getWritableDatabase();
switch (match) {
case WORDS:
return database.delete(WordEntry.TABLE_NAME, selection, selectionArgs);
case WORD_ID:
long wordId = ContentUris.parseId(uri);
selection = WordEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(wordId)};
return database.delete(WordEntry.TABLE_NAME, selection, selectionArgs);
default:
throw new IllegalArgumentException("Delete is not supported for " + uri);
}
}
#Override
public int update(#NonNull Uri uri, #Nullable ContentValues values, #Nullable String selection, #Nullable String[] selectionArgs) {
int match = sUriMatcher.match(uri);
switch (match) {
case WORDS:
return updateWord(uri, values, selection, selectionArgs);
case WORD_ID:
long wordId = ContentUris.parseId(uri);
selection = WordEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(wordId)};
return updateWord(uri, values, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
}
}
private Uri insertWord(Uri uri, ContentValues values) {
if (values.containsKey(WordEntry.COLUMN_NAME_CIRCASSIAN)) {
String circassian = values.getAsString(WordEntry.COLUMN_NAME_TURKISH);
if (circassian == null) {
throw new IllegalArgumentException("Word requires circassian translation");
}
}
if (values.containsKey(WordEntry.COLUMN_NAME_TURKISH)) {
String turkish = values.getAsString(WordEntry.COLUMN_NAME_TURKISH);
if (turkish == null) {
throw new IllegalArgumentException("Word requires turkish translation");
}
}
if (values.size() > 0) {
return null;
}
SQLiteDatabase database = wordDbHelper.getWritableDatabase();
long idOfNewlyInserted = database.insert(WordEntry.TABLE_NAME, null, values);
if (idOfNewlyInserted == -1) {
Log.e(LOG_TAG, "Failed to insert row for: " + uri);
return null;
}
return ContentUris.withAppendedId(BASE_CONTENT_URI, idOfNewlyInserted);
}
private int updateWord(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
if (values.containsKey(WordEntry.COLUMN_NAME_CIRCASSIAN)) {
String circassian = values.getAsString(WordEntry.COLUMN_NAME_CIRCASSIAN);
if (circassian == null) {
throw new IllegalArgumentException("Circassian translation required ");
}
}
if (values.containsKey(WordEntry.COLUMN_NAME_TURKISH)) {
String turkish = values.getAsString(WordEntry.COLUMN_NAME_TURKISH);
if (turkish == null) {
throw new IllegalArgumentException("Turkish translation required");
}
}
if (values.size() < 0) {
return 0;
}
SQLiteDatabase database = wordDbHelper.getWritableDatabase();
return database.update(WordEntry.TABLE_NAME, values, selection, selectionArgs);
}
}
I've changed the CONTENT_AUTHORITY String to the
com.tur_cirdictionary.turkish_circassiandictionary
making it same with the package name. That solved the problem.
I'm trying to share some data between my apps using ContentProvider. I have a problem with accessing the data with resolver in the second app: Cursor object is null after query. Can't figure out where I made a mistake in the code so I'm asking for help. Code of the first app which provides the data :
Provider in manifest :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.piotr.planer">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".BroadcastEvent" />
<provider
android:authorities="com.example.piotr.planer.PlansProvider"
android:name=".PlansProvider"
android:exported="true"
android:multiprocess="true" >
</provider>
</application>
Provider class:
package com.example.piotr.planer;
public class PlansProvider extends ContentProvider {
// Unique namespace for provider
static final String PROVIDER_NAME = "com.example.piotr.planer.PlansProvider";
static final String URL = "content://" + PROVIDER_NAME + "/cpplans";
static final Uri CONTENT_URL = Uri.parse(URL);
// Defining columns in database
static final String id = "id";
static final String name = "name";
static final String date = "date";
static final int uriCode = 1;
private static HashMap<String, String> values;
// UriMatcher matches our unique Uris with our ContentProviders
static final UriMatcher uriMatcher;
// assigning these unique Uris
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "cpplans", uriCode);
}
// Defining database
private SQLiteDatabase sqlDB;
static final String DATABASE_NAME = "myPlans";
static final String TABLE_NAME = "plans";
static final int DATABASE_VERSION = 1;
static final String CREATE_DB_TABLE = "CREATE TABLE " + TABLE_NAME +
" (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
" name TEXT NOT NULL, " +
" date INTEGER);";
#Override
public boolean onCreate() {
// Creates Database
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
sqlDB = dbHelper.getWritableDatabase();
if (sqlDB != null) {
return true;
}
return false;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
switch (uriMatcher.match(uri)) {
case uriCode:
queryBuilder.setProjectionMap(values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
Cursor cursor = queryBuilder.query(sqlDB, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
#Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case uriCode:
return "vnd.android.cursor.dir/cpplans";
default:
throw new IllegalArgumentException("Unsupported URI " + uri);
}
}
#Override
public Uri insert(Uri uri, ContentValues values) {
long rowID = sqlDB.insert(TABLE_NAME, null, values);
if (rowID > 0) {
Uri _uri = ContentUris.withAppendedId(CONTENT_URL, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return uri;
} else {
Toast.makeText(getContext(), "Row insert failed", Toast.LENGTH_SHORT).show();
return null;
}
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int rowsDeleted = 0;
switch (uriMatcher.match(uri)) {
case uriCode:
rowsDeleted = sqlDB.delete(TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int rowsUpdated = 0;
switch (uriMatcher.match(uri)) {
case uriCode:
rowsUpdated = sqlDB.update(TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase sqlDB) {
sqlDB.execSQL(CREATE_DB_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase sqlDB, int oldVersion, int newVersion) {
sqlDB.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(sqlDB);
}
}
}
The addPlanToProviderFunction() method:
private void addPlanToProvider(long timeInMillis, String eventName) {
ContentValues values = new ContentValues();
values.put(PlansProvider.name, eventName);
values.put(PlansProvider.date, timeInMillis);
Uri uri = mContext.getContentResolver().insert(PlansProvider.CONTENT_URL, values);
}
The class in the second app where I want to get the data:
package com.example.piotr.zadaniowiec;
public class PlanList extends AppCompatActivity {
static final Uri CONTENT_URL = Uri.parse("content://com.example.piotr.planer.PlansProvider.cpplans");
ContentResolver resolver;
TextView planListTextView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plans_list);
planListTextView = findViewById(R.id.plan_list_text_view);
resolver = getContentResolver();
getPlans();
}
private void getPlans() {
String[] projection = new String[]{"id", "name", "date"};
Cursor cursor = resolver.query(CONTENT_URL, projection, null, null, null);
String planList = "";
if (cursor.moveToNext()) { // I get an error here
do {
String id = cursor.getString(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
long date = cursor.getLong(cursor.getColumnIndex("date"));
planList = planList + id + name + date + "\n";
} while (cursor.moveToNext());
}
planListTextView.setText(planList);
}
}
I get :
"NullPointerException: Attempt to invoke interface method 'boolean android.database.Cursor.moveToNext()' on a null object reference."
Thanks anyone for the help.
Your CONTENT_URL is in the wrong format - you need a / after the authority, not a .
static final Uri CONTENT_URL =
Uri.parse("content://com.example.piotr.planer.PlansProvider/cpplans");
As it is now, you're looking for the root path for a ContentProvider that has an authority of com.example.piotr.planer.PlansProvider.cpplans, which doesn't exist, hence the null Cursor.
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
I have a ContentProvider that abstracts the info I'm using for a CursorAdapter to populate a list.
I'm building a SearchView in the action bar and want to attach a search ContentProvider to it. Can I use the same provider for this? Is it good design?
The SearchView content would be the same as in #1, it's just used to search a massive listview of items.
The ContentProvider code is below:
public class WebSitesContentProvider extends ContentProvider {
// database
private UserDatabaseHelper database;
// Used for the UriMacher
private static final int TOTAL_ELEMENTS = 10;
private static final int ELEMENT_ID = 20;
private static final String BASE_PATH = "websites";
public static final Uri CONTENT_URI = Uri.parse("content://" + Consts.AUTHORITY + "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/website";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + Consts.TABLE_WEBSITES_INFO;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(Consts.AUTHORITY, BASE_PATH, TOTAL_ELEMENTS);
sURIMatcher.addURI(Consts.AUTHORITY, BASE_PATH + "/#", ELEMENT_ID);
}
#Override
public boolean onCreate() {
database = new UserDatabaseHelper(getContext());
return false;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// UsIng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Check if the caller has requested a column which does not exists
checkColumns(projection);
// Set the table
queryBuilder.setTables(Consts.TABLE_WEBSITES_INFO);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TOTAL_ELEMENTS:
break;
case ELEMENT_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(Consts.COLUMN_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 cursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
long id = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
id = sqlDB.insert(Consts.TABLE_WEBSITES_INFO, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO,
selection,
selectionArgs);
break;
case ELEMENT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO,
Consts.COLUMN_ID + "=" + id,
null);
} else {
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO,
Consts.COLUMN_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
/*
* Let's not notify content observers on deletes of less then 1 as each delete would cause a network call.
* user could delete multiple entries at once. if the deletes are greater then 1 then it's probably a
* request to remove the entire list, this we will allow
*/
//if(rowsDeleted>1)
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO,
values,
selection,
selectionArgs);
break;
case ELEMENT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO,
values,
Consts.COLUMN_ID + "=" + id,
null);
} else {
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO,
values,
Consts.COLUMN_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private void checkColumns(String[] projection) {
String[] available = {
Consts.COLUMN_USER,
Consts.COLUMN_ID
};
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 columns in projection");
}
}
}
}
UPDATE
ContentProviders are generic and I agree on this.
They can be adapted.
So I adapted mine to handle SearchView data content.
Below is the entire ContentProvider acting as both a SearchView content provider and a ListView cursor adapter for anyone interested:
package org.jefferyemanuel.database;
import java.util.Arrays;
import java.util.HashSet;
import org.jefferyemanuel.bulkwebsites.Consts;
import org.jefferyemanuel.bulkwebsites.Utils;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
public class WebSitesContentProvider extends ContentProvider {
/* define out search provider structures */
// UriMatcher constant for search suggestions
private static final int SEARCH_SUGGEST = 1;
private static final String[] SEARCH_SUGGEST_COLUMNS = {
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID
};
// database
private UserDatabaseHelper database;
// Used for the UriMacher
private static final int TOTAL_ELEMENTS = 10;
private static final int ELEMENT_ID = 20;
private static final String BASE_PATH = "websites";
public static final Uri CONTENT_URI = Uri.parse("content://" + Consts.AUTHORITY + "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/website";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + Consts.TABLE_WEBSITES_INFO;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(Consts.AUTHORITY, BASE_PATH, TOTAL_ELEMENTS);
sURIMatcher.addURI(Consts.AUTHORITY, BASE_PATH + "/#", ELEMENT_ID);
sURIMatcher.addURI(Consts.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
sURIMatcher.addURI(Consts.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
}
#Override
public boolean onCreate() {
database = new UserDatabaseHelper(getContext());
return false;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// UsIng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Check if the caller has requested a column which does not exists
checkColumns(projection);
// Set the table
queryBuilder.setTables(Consts.TABLE_WEBSITES_INFO);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TOTAL_ELEMENTS:
break;
case SEARCH_SUGGEST:
queryBuilder.appendWhere(Consts.COLUMN_NAME + " LIKE '%" + uri.getLastPathSegment() + "%'");
break;
case ELEMENT_ID:
// Adding the ID to the original query
queryBuilder.appendWhere(Consts.COLUMN_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);
/*
* If this request is from a SearchView then convert cursor to search Matrix cursor.
*/
if (uriType == SEARCH_SUGGEST)
cursor = buildSearchMatrixCursorFromStandardCursor(cursor);
// Make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
long id = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
id = sqlDB.insert(Consts.TABLE_WEBSITES_INFO, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO, selection, selectionArgs);
break;
case ELEMENT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO,
Consts.COLUMN_ID + "=" + id,
null);
} else {
rowsDeleted = sqlDB.delete(Consts.TABLE_WEBSITES_INFO,
Consts.COLUMN_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case TOTAL_ELEMENTS:
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO, values,
selection, selectionArgs);
break;
case ELEMENT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO, values,
Consts.COLUMN_ID + "=" + id,
null);
} else {
rowsUpdated = sqlDB.update(Consts.TABLE_WEBSITES_INFO, values,
Consts.COLUMN_ID + "=" + id + " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private void checkColumns(String[] projection) {
String[] available = { Consts.COLUMN_NAME, Consts.COLUMN_ID };
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 columns in projection");
}
}
}
/*
* KEY METHOD THAT USES THE DATA FROM DATABASE THAT LISTVIEW ALSO USES
* TO CREATE A MATRIX CURSOR TO SEND BACK TO SEARCHVIEW
*/
private Cursor buildSearchMatrixCursorFromStandardCursor(Cursor cursor) {
MatrixCursor cursorSearch = new MatrixCursor(SEARCH_SUGGEST_COLUMNS);
int id = 0;
int index = cursor.getColumnIndex(Consts.COLUMN_NAME);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
String name = cursor.getString(index);
cursorSearch.addRow(new String[] {
String.valueOf(++id),
name,
name,
name,
SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT
});
cursor.moveToNext();
}
return cursorSearch;
}
}
The ContentProvider you're using is generic and can be used for a search feature.
You may think at first that maybe you should write a ContentProvider that specifically works for search but:
You will have to duplicate a lot of code.
ContentProviders are not designed to be specific, they just abstract the access to data.
Your "Search Content Provider" will be used to make queries and you already have that with the Content Provider you have.
The ContentProvider doesn't need to be specific to search.
On the other hand, your Adpater should be specific.
EDIT
I get a java.lang.IllegalArgumentException: Unsupported URI: content://com.example.locationreminder.remindercontentprovider/items
Shouldn't I be able to pick whatever name I want for the AUTHORITY string? as well as the content URI?
I have the following code:
public class ReminderContentProvider extends ContentProvider {
private DatabaseHelper mDbHelper;
public static final String AUTHORITY = "com.example.locationreminder.remindercontentprovider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");
public static final String TABLE_NAME = "reminder";
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
public static interface ReminderColumns extends BaseColumns {
public static final Uri CONTENT_URI = ReminderContentProvider.CONTENT_URI;
public static final String TITLE = "Title";
public static final String DATE = "Date";
public static final String CONTENT_PATH = "items";
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.locationreminder.items";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.locationreminder.items";
}
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, ReminderColumns.CONTENT_PATH, ALLROWS);
sUriMatcher.addURI(AUTHORITY, ReminderColumns.CONTENT_PATH + "/#", SINGLE_ROW);
}
#Override
public boolean onCreate() {
mDbHelper = new DatabaseHelper(getContext());
return true;
}
#Override
public String getType(Uri uri) {
switch(sUriMatcher.match(uri)) {
case ALLROWS:
return ReminderColumns.CONTENT_TYPE;
case SINGLE_ROW:
return ReminderColumns.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(DatabaseHelper.REMINDER_TABLE_NAME);
switch (sUriMatcher.match(uri)) {
case ALLROWS:
// all nice and well
break;
case SINGLE_ROW:
// limit query to one row at most:
builder.appendWhere(ReminderColumns._ID + " = " + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
Cursor cursor = builder.query(mDbHelper.getWritableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
// if we want to be notified of any changes:
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
Note: I also implemented insert(), update() and delete() which aren't relevant for the error I get.
The manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.locationreminder"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<provider
android:name="com.example.locationreminder.ReminderContentProvider"
android:authorities="com.example.locationreminder.remindercontentprovider"
android:singleUser="false" /> <!-- other users can't use this provider -->
<activity
android:name="com.example.locationreminder.MainActivity"
android:theme="#android:style/Theme.Holo.Light"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.locationreminder.NewReminderActivity"
android:label="#string/title_activity_new_reminder"
android:theme="#style/CalendarTheme.WithActionBar" >
</activity>
</application>
EDIT
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_Holo_Light);
setContentView(R.layout.activity_main);
Cursor cursor = managedQuery(ReminderContentProvider.CONTENT_URI, null, null, null, null);
mSimpleCursorAdapter = new SpecialAdapter(this,
R.layout.row,
cursor,
new String[]{ DatabaseHelper.KEY_ID, DatabaseHelper.KEY_TITLE, DatabaseHelper.KEY_DATE }
new int[] { R.id.titleID, R.id.dateTimeOrLocationID },
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
getLoaderManager().initLoader(0, null, this);
ListView listView = (ListView) findViewById(R.id.list);
listView.setAdapter(mSimpleCursorAdapter);
}
Does the error happen when switch (sUriMatcher.match(uri)) falls to default case?
If so, then you need to pass uri that matches ALLROWS or SINGLE_ROW