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");
}
}
}
}
ContentProvider
s 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:
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.