Reputation: 729
I have three tables and would like to use a content provider to manage them.Below is code from my content provider:
private static final UriMatcher sURIMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, METER_PATH, all_meters);
sURIMatcher.addURI(AUTHORITY, METER_PATH + "/#", single_meter);
sURIMatcher.addURI(AUTHORITY, CUSTOMERS_PATH, all_customers);
sURIMatcher.addURI(AUTHORITY, CUSTOMERS_PATH + "/#", single_customer);
sURIMatcher.addURI(AUTHORITY, BILL_PATH, all_bills);
sURIMatcher.addURI(AUTHORITY, BILL_PATH + "/#", single_bill);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = database.getWritableDatabase();
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder
.setTables(MeterTableDetails.TABLE_METERS
+ " as meters "
+ " INNER JOIN "
+ CustomerTableDetails.TABLE_CUSTOMERS
+ " as customers "
+ " ON "
+ (MeterTableDetails.METER_ID = CustomerTableDetails.KEY_METER_ID)
+ " INNER JOIN "
+ WaterBillTableDetails.TABLE_WATER_BILL
+ " as waterbills "
+ " ON "
+ (CustomerTableDetails.KEY_METER_ID = WaterBillTableDetails.BILL_METER_ID));
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case all_meters:
break;
case single_meter:
// Adding the ID to the original query
String id = uri.getPathSegments().get(1);
queryBuilder.appendWhere(MeterTableDetails.METER_ID + "=" + id);
break;
case all_customers:
break;
case single_customer:
// Adding the ID to the original query
String id1 = uri.getPathSegments().get(1);
queryBuilder.appendWhere(CustomerTableDetails.KEY_CUSTOMER_ID + "="
+ id1);
break;
case all_bills:
break;
case single_bill:
// Adding the ID to the original query
String id2 = uri.getPathSegments().get(1);
queryBuilder.appendWhere(WaterBillTableDetails.BILL_ID + "=" + id2);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
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;
}
I have three tables,and have created some joins in the querybuilder.setTables method.I'm trying to display meter items in a list from the meters table.I also have a SimpleCursorAdapter with loaderCallbacks implementation. Currently i get the following error in my logcat and i think it's because of the joins and the query:
03-20 15:11:59.692: E/SQLiteCursor(2001): requesting column name with table name -- meters._id
03-20 15:11:59.692: E/SQLiteCursor(2001): java.lang.Exception
03-20 15:11:59.692: E/SQLiteCursor(2001): at android.database.sqlite.SQLiteCursor.getColumnIndex(SQLiteCursor.java:180)
03-20 15:11:59.692: E/SQLiteCursor(2001): at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:301)
03-20 15:11:59.692: E/SQLiteCursor(2001): at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78)
03-20 15:11:59.692: E/SQLiteCursor(2001): at android.support.v4.widget.SimpleCursorAdapter.findColumns(SimpleCursorAdapter.java:317)
03-20 15:11:59.692: E/SQLiteCursor(2001): at android.support.v4.widget.SimpleCursorAdapter.swapCursor(SimpleCursorAdapter.java:328)
03-20 15:11:59.692: E/SQLiteCursor(2001): at com.isys.waterbillingsystem.MetersActivity.onLoadFinished(MetersActivity.java:180)
03-20 15:11:59.692: E/SQLiteCursor(2001): at com.isys.waterbillingsystem.MetersActivity.onLoadFinished(MetersActivity.java:1)
EDIT
private static final String CREATE_CUSTOMER_VIEW = ""
+ "CREATE VIEW " + TABLE_CUSTOMER_VIEW
+ " AS SELECT "+MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID+" AS "+ MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID +","+
" "+CustomerTableDetails.KEY_FIRST_NAME+","+
" "+CustomerTableDetails.KEY_LAST_NAME+","+
" "+CustomerTableDetails.KEY_METER_ID+","+
" "+CustomerTableDetails.KEY_METER_NUMBER+","+
" "+CustomerTableDetails.KEY_PLOT_NUMBER+","+
" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+ CustomerTableDetails.KEY_CUSTOMER_ID+
" FROM "+CustomerTableDetails.TABLE_CUSTOMERS+" AS customers "+" INNER JOIN "+MeterTableDetails.TABLE_METERS+" AS meters"+
" ON "+CustomerTableDetails.KEY_METER_ID+" = "+MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID;
public static TableDescriptor getDescriptor() {
TableDescriptor descriptor = new TableDescriptor();
descriptor.setTableName(TABLE_CUSTOMER_VIEW);
descriptor.setColumnId(CUSTOMER_VIEW_ID);
String[] available = { ViewCustomers.CUSTOMER_VIEW_ID,
ViewCustomers.CUSTOMER_VIEW_LASTNAME,
ViewCustomers.CUSTOMER_VIEW_LASTNAME,
ViewCustomers.CUSTOMER_VIEW_KEY_METER_ID,
ViewCustomers.CUSTOMER_VIEW_METER,
ViewCustomers.CUSTOMER_VIEW_PLOT};
descriptor.setAvailableColumns(available);
return descriptor;
}
EDIT 2
private static final String CREATE_METER_READING_VIEW = ""
+ "CREATE VIEW " + TABLE_METER_READING_VIEW
+ " AS SELECT " + WaterBillTableDetails.TABLE_WATER_BILL+ ".*"
+ ", " +CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_METER_NUMBER+","
+" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_PLOT_NUMBER+","
+" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_ACCOUNT_NUMBER+","
+" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_METER_ID+""
+" FROM "+WaterBillTableDetails.TABLE_WATER_BILL+" AS waterbills "+" JOIN "+CustomerTableDetails.TABLE_CUSTOMERS+" AS customers"
+" ON "+WaterBillTableDetails.BILL_CUSTOMER_ID+" ="+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_CUSTOMER_ID;
Logcat error
03-25 10:45:03.476: E/AndroidRuntime(1144): FATAL EXCEPTION: main
03-25 10:45:03.476: E/AndroidRuntime(1144): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.isys.waterbillingsystem/com.isys.waterbillingsystem.CustomerDetailsAccountsActivity}: java.lang.NullPointerException
03-25 10:45:03.476: E/AndroidRuntime(1144): Caused by: java.lang.NullPointerException
03-25 10:45:03.476: E/AndroidRuntime(1144): at com.isys.waterbillingsystem.CustomerDetailsAccountsActivity.onCreate(CustomerDetailsAccountsActivity.java:48 )
Upvotes: 3
Views: 6872
Reputation: 2867
Here is a quick example for Views:
public class HektorDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "hektor.db";
private static final int DATABASE_VERSION = 91;
public HektorDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Method is called during creation of the database
@Override
public void onCreate(SQLiteDatabase database) {
AppointmentTypesTable.onCreate(database);
}
// Method is called during an upgrade of the database,
// e.g. if you increase the database version
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
AppointmentTypesTable.onUpgrade(database, oldVersion, newVersion);
}
}
This is a standard SQLiteOpenHelper.
public class AppointmentWithTypeAndContactsView {
public static final String TABLE_NAME = "appointments_with_type_and_contacts";
public static final String COLUMN_ID = AppointmentsTable.COLUMN_ID;
public static final String COLUMN_EXTERNAL_ID = AppointmentsTable.COLUMN_EXTERNAL_ID;
public static final String COLUMN_START_DATE = AppointmentsTable.COLUMN_START_DATE;
private static final String DATABASE_CREATE = ""
+ "CREATE VIEW " + TABLE_NAME
+ " AS SELECT " + AppointmentsTable.TABLE_NAME + ".*"
+ ", " + AppointmentTypesTable.TABLE_NAME + "." + AppointmentTypesTable.COLUMN_NAME
+ ", " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_TITLE + " || ' ' || " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_LAST_NAME + " || ' ' || " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_FIRST_NAME + " AS " + BuyersTable.COLUMN_LAST_NAME
+ ", " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_TITLE + " || ' ' || " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_LAST_NAME + " || ' ' || " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_FIRST_NAME + " AS " + SellersDetailsTable.COLUMN_LAST_NAME
+ " FROM " + AppointmentsTable.TABLE_NAME + " LEFT OUTER JOIN " + AppointmentTypesTable.TABLE_NAME
+ " ON " + AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_TYPE
+ " = " + AppointmentTypesTable.TABLE_NAME + "." + AppointmentTypesTable.COLUMN_EXTERNAL_ID
+ " LEFT OUTER JOIN " + BuyersTable.TABLE_NAME
+ " ON " + AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_BUYER
+ " = " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_EXTERNAL_ID
+ " LEFT OUTER JOIN " + SellersDetailsTable.TABLE_NAME
+ " ON " + AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_SELLER
+ " = " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_EXTERNAL_ID;
public static void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
public static void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
Log.w(AppointmentWithTypeAndContactsView.class.getName(), "Upgrading database from version "
+ oldVersion + " to " + newVersion
+ ", which will destroy all old data");
database.execSQL("DROP VIEW IF EXISTS " + TABLE_NAME);
onCreate(database);
}
public static TableDescriptor getDescriptor() {
TableDescriptor descriptor = new TableDescriptor();
descriptor.setTableName(TABLE_NAME);
descriptor.setColumnId(COLUMN_ID);
String[] appointmentsAvailableColumns = AppointmentsTable.getDescriptor().getAvailableColumns();
String[] typesAvailableColumns = new String[] {AppointmentTypesTable.COLUMN_NAME};
String[] buyersAvailableColumns = new String[] {BuyersTable.COLUMN_LAST_NAME};
String[] sellerssAvailableColumns = new String[] {SellersDetailsTable.COLUMN_LAST_NAME};
descriptor.setAvailableColumns(ArrayUtils.concatAll(appointmentsAvailableColumns, typesAvailableColumns, buyersAvailableColumns, sellerssAvailableColumns));
return descriptor;
}
}
I've updated the Table class to include some utility methods.
public class TableDescriptor {
private String tableName;
private String columnId;
private String[] availableColumns;
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getColumnId() {
return columnId;
}
public void setColumnId(String columnId) {
this.columnId = columnId;
}
public String[] getAvailableColumns() {
return availableColumns;
}
public void setAvailableColumns(String[] availableColumns) {
this.availableColumns = availableColumns;
}
}
TableDescriptor
is just a container class.
public final class HektorContentProviderContract {
public static final String AUTHORITY = "fr.intuitiv.hektor.contentprovider";
public static final String APPOINTMENT_WITH_TYPE_BASE_PATH = "appointment_with_type";
public static final Uri APPOINTMENT_WITH_TYPE_CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + APPOINTMENT_WITH_TYPE_BASE_PATH);
public static final String APPOINTMENT_WITH_TYPE_CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/vnd." + AUTHORITY + "." + APPOINTMENT_WITH_TYPE_BASE_PATH;
public static final String APPOINTMENT_WITH_TYPE_CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/vnd." + AUTHORITY + "." + APPOINTMENT_WITH_TYPE_BASE_PATH;
}
I usually create some "Contract" classes to store any public constants.
public class ContentProviderHelper {
private Context context;
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
public ContentProviderHelper(Context context) {
this.setContext(context);
}
public Cursor query(SQLiteOpenHelper database, TableDescriptor table,
boolean isSingular, Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Uisng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Set the table
queryBuilder.setTables(table.getTableName());
if (isSingular) {
queryBuilder.appendWhere(table.getColumnId() + "="
+ uri.getLastPathSegment());
}
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;
}
public Uri insert(SQLiteOpenHelper database, TableDescriptor table, Uri uri, ContentValues values) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
long id = 0;
id = sqlDB.insertWithOnConflict(table.getTableName(), null, values, SQLiteDatabase.CONFLICT_REPLACE);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.withAppendedPath(getTableUri(table), Long.toString(id));
}
public int delete(SQLiteOpenHelper database, TableDescriptor table, boolean isSingular, Uri uri, String selection, String[] selectionArgs) {
int rowsDeleted = 0;
SQLiteDatabase sqlDB = database.getWritableDatabase();
if (!isSingular) {
rowsDeleted = sqlDB.delete(table.getTableName(), selection,
selectionArgs);
} else {
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(table.getTableName(),
table.getColumnId() + "=" + id, null);
} else {
rowsDeleted = sqlDB.delete(table.getTableName(),
table.getColumnId() + "=" + id + " and " + selection,
selectionArgs);
}
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
public int update(SQLiteOpenHelper database, TableDescriptor table, boolean isSingular, Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
if (!isSingular) {
rowsUpdated = sqlDB.update(table.getTableName(), values, selection,
selectionArgs);
} else {
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(table.getTableName(), values,
table.getColumnId() + "=" + id, null);
} else {
rowsUpdated = sqlDB.update(table.getTableName(), values,
table.getColumnId() + "=" + id + " and " + selection,
selectionArgs);
}
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
public void checkColumns(TableDescriptor table, String[] projection) {
String[] available = table.getAvailableColumns();
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");
}
}
}
protected Uri getTableUri(TableDescriptor table) {
Uri result = null;
String tableName = table.getTableName();
if (AppointmentWithTypeView.TABLE_NAME.equals(tableName)) {
result = HektorContentProviderContract.APPOINTMENT_WITH_TYPE_CONTENT_URI;
}
return result;
}
}
Here is another utility class I'm using when working with ContentProviders. It simplifies the database management operations. It is quite handy if you have a lot of tables to manage.
public class UriDescriptor {
private TableDescriptor table;
private boolean singular;
public boolean isSingular() {
return singular;
}
public void setSingular(boolean singular) {
this.singular = singular;
}
public TableDescriptor getTable() {
return table;
}
public void setTable(TableDescriptor table) {
this.table = table;
}
}
This is again just a container class - not really interesting.
public class HektorContentProvider extends ContentProvider {
private ContentProviderHelper helper;
// database
private HektorDatabaseHelper database;
// Used for the UriMacher
private static final int APPOINTMENT_WITH_TYPE = 290;
private static final int APPOINTMENT_WITH_TYPE_ID = 300;
private static final UriMatcher sURIMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(HektorContentProviderContract.AUTHORITY,
HektorContentProviderContract.APPOINTMENT_WITH_TYPE_BASE_PATH, APPOINTMENT_WITH_TYPE);
sURIMatcher.addURI(HektorContentProviderContract.AUTHORITY,
HektorContentProviderContract.APPOINTMENT_WITH_TYPE_BASE_PATH + "/#",
APPOINTMENT_WITH_TYPE_ID);
}
@Override
public boolean onCreate() {
database = new HektorDatabaseHelper(getContext());
helper = new ContentProviderHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
UriDescriptor descriptor = getDescriptor(uri);
helper.checkColumns(descriptor.getTable(), projection);
Cursor cursor = helper.query(database, descriptor.getTable(),
descriptor.isSingular(), uri, projection, selection,
selectionArgs, sortOrder);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
UriDescriptor descriptor = getDescriptor(uri);
Uri result = helper
.insert(database, descriptor.getTable(), uri, values);
return result;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int rowsDeleted = 0;
UriDescriptor descriptor = getDescriptor(uri);
rowsDeleted = helper.delete(database, descriptor.getTable(),
descriptor.isSingular(), uri, selection, selectionArgs);
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
UriDescriptor descriptor = getDescriptor(uri);
int rowsUpdated = helper.update(database, descriptor.getTable(),
descriptor.isSingular(), uri, values, selection, selectionArgs);
return rowsUpdated;
}
protected UriDescriptor getDescriptor(Uri uri) {
UriDescriptor descriptor = new UriDescriptor();
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case APPOINTMENT_WITH_TYPE:
descriptor.setSingular(false);
descriptor.setTable(AppointmentWithTypeView.getDescriptor());
break;
case APPOINTMENT_WITH_TYPE_ID:
descriptor.setSingular(true);
descriptor.setTable(AppointmentWithTypeView.getDescriptor());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return descriptor;
}
}
That's the ContentProvider class. It is quite simple, since most of the work is done in the ContentProviderHelper
class.
This is a class representing a View. I write such classes for each View or Table I want to create in my database. Both entities are pretty much used the same way - you just have to change the SQL statement from CREATE VIEW to CREATE TABLE. As you can see, views can be created based on a SELECT statement - so they are very useful if you want to join several tables. When inserting to any of the AppointmentsTable
/ AppointmentTypesTable
tables the data would be available via the View too. So I prefer to create such View and a ContentProvider
working over it. SELECT queries are simple (just read from the View). You would have to handle INSERT / DELETES differently though - i.e. insert the data to the associated table.
My application loads its data from web service, so I'm doing it on the background. I've create CONTENT_URL in the ContentProvider for each table (i.e. AppointmentsTable
and AppointmentTypesTable
). Those are used by the background process to insert / update the data. The UI uses only the CONTENT_URLs connected to the Views, since they just need to read the data.
Let me know if you've got the idea. I could share some more code if needed. :)
Upvotes: 6