I know this has been asked a couple of times before, but in all those questions neither the OP\'s nor the people who answered, provided clear examples.
So what I\'m
do you think you could give some hints on how to do it
This has nothing much to do with Android, and even not all that much to do with Java. Decomposing long programming structures (e.g., classes in Java) into smaller structures has standard techniques, called design patterns, with language-specific implementations.
For example, you could go with the composite pattern:
Define an interface -- I'll call it TableHelper
here -- that has onCreate()
and onUpdate()
methods that match those on SQLiteOpenHelper
Define N classes, one per table, that implement the TableHelper
interface and provide the create and upgrade logic for that table (along with whatever other business logic you want to have on those classes)
Have your SQLiteOpenHelper
define a TableHelper[]
containing instances of your TableHelper
classes, and have it delegate onCreate()
and onUpgrade()
to those TableHelper
instances by iterating over the array
Using a Content Provider, you can split your long SQLiteOpenHelper in a multiple files : one persistenceContract for each table, one little SQLiteOpenHelper, and long and verbose ContentProvider.
With this solution, you need to write more code. But it's more readable, and easy to maintain.
Avantages :
Disadvantage :
Let's try with your initial code !
1. Create a PersistenceContract for each table
ListPersistenceContract :
public final class ListPersistenceContract {
public static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID;
public static final String CONTENT_LIST_TYPE = "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + ListEntry.TABLE_NAME;
public static final String CONTENT_LIST_ITEM_TYPE = "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + ListEntry.TABLE_NAME;
public static final String VND_ANDROID_CURSOR_ITEM_VND = "vnd.android.cursor.item/vnd." + CONTENT_AUTHORITY + ".";
private static final String CONTENT_SCHEME = "content://";
public static final Uri BASE_CONTENT_URI = Uri.parse(CONTENT_SCHEME + CONTENT_AUTHORITY);
private static final String VND_ANDROID_CURSOR_DIR_VND = "vnd.android.cursor.dir/vnd." + CONTENT_AUTHORITY + ".";
private static final String SEPARATOR = "/";
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
private ListPersistenceContract() {}
public static Uri getBaseListUri(String listId) {
return Uri.parse(CONTENT_SCHEME + CONTENT_LIST_ITEM_TYPE + SEPARATOR + listId);
}
/* Inner class that defines the table contents */
public static abstract class ListEntry implements BaseColumns {
public static final String TABLE_NAME = "list";
public static final String COLUMN_LIST_NAME = "list_name";
public static final Uri CONTENT_LIST_URI = BASE_CONTENT_URI.buildUpon().appendPath(TABLE_NAME).build();
public static String[] LIST_COLUMNS = new String[]{
ListPersistenceContract.ListEntry._ID,
ListEntry.COLUMN_LIST_NAME};
public static final String LIST_AND_TASK = "listandtask";
public static Uri buildListUriWith(long id) {
return ContentUris.withAppendedId(CONTENT_LIST_URI, id);
}
public static Uri buildListUriWith(String id) {
Uri uri = CONTENT_LIST_URI.buildUpon().appendPath(id).build();
return uri;
}
public static Uri buildListUri() {
return CONTENT_LIST_URI.buildUpon().build();
}
public static Uri buildListAndTaskUri() {
return BASE_CONTENT_URI.buildUpon().appendPath(ListEntry.LIST_AND_TASK).build();
}
}
}
TaskPersistenceContract :
public class TaskPersistenceContract {
public static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID;
public static final String CONTENT_TASK_TYPE = "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + TaskEntry.TABLE_NAME;
public static final String CONTENT_TASK_ITEM_TYPE = "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + TaskEntry.TABLE_NAME;
public static final String VND_ANDROID_CURSOR_ITEM_VND = "vnd.android.cursor.item/vnd." + CONTENT_AUTHORITY + ".";
private static final String CONTENT_SCHEME = "content://";
public static final Uri BASE_CONTENT_URI = Uri.parse(CONTENT_SCHEME + CONTENT_AUTHORITY);
private static final String VND_ANDROID_CURSOR_DIR_VND = "vnd.android.cursor.dir/vnd." + CONTENT_AUTHORITY + ".";
private static final String SEPARATOR = "/";
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
private TaskPersistenceContract() {}
public static Uri getBaseTaskUri(String taskId) {
return Uri.parse(CONTENT_SCHEME + CONTENT_TASK_ITEM_TYPE + SEPARATOR + taskId);
}
/* Inner class that defines the table contents */
public static abstract class TaskEntry implements BaseColumns {
public static final String TABLE_NAME = "task";
public static final String COLUMN_TASK_LIST_ID = "list_id";
public static final String COLUMN_TASK_NAME = "task_name";
public static final Uri CONTENT_TASK_URI = BASE_CONTENT_URI.buildUpon().appendPath(TABLE_NAME).build();
public static String[] TASK_COLUMNS = new String[]{
TaskPersistenceContract.TaskEntry._ID,
TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID,
TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME};
public static Uri buildTaskUriWith(long id) {
return ContentUris.withAppendedId(CONTENT_TASK_URI, id);
}
public static Uri buildTaskUriWith(String id) {
Uri uri = CONTENT_TASK_URI.buildUpon().appendPath(id).build();
return uri;
}
public static Uri buildTaskUri() {
return CONTENT_TASK_URI.buildUpon().build();
}
}
}
2. Create DbHelper
public class LocalDbHelper {
public static final int DB_VERSION = 1;
public static final String DB_NAME = "mydatabase.db";
private static final String TEXT_TYPE = " TEXT";
private static final String INTEGER_TYPE = " INTEGER";
private static final String PRIMARY_KEY = " PRIMARY KEY";
private static final String AUTOINCREMENT = " AUTOINCREMENT";
private static final String UNIQUE = " UNIQUE";
private static final String CREATE_TABLE = "CREATE TABLE ";
private static final String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS ";
private static final String OPEN_PARENTHESIS = " (";
private static final String CLOSE_PARENTHESIS = " )";
private static final String COMMA_SEP = ",";
private static final String CREATE_LIST_TABLE =
CREATE_TABLE + ListPersistenceContract.ListEntry.TABLE_NAME + OPEN_PARENTHESIS +
ListPersistenceContract.ListEntry._ID + INTEGER_TYPE + PRIMARY_KEY + AUTOINCREMENT + COMMA_SEP +
ListPersistenceContract.ListEntry.COLUMN_LIST_NAME + TEXT_TYPE + UNIQUE +
CLOSE_PARENTHESIS;
private static final String CREATE_TASK_TABLE =
CREATE_TABLE + TaskPersistenceContract.TaskEntry.TABLE_NAME + OPEN_PARENTHESIS +
TaskPersistenceContract.TaskEntry._ID + INTEGER_TYPE + PRIMARY_KEY + AUTOINCREMENT + COMMA_SEP +
TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID + INTEGER_TYPE + COMMA_SEP +
TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME + TEXT_TYPE +
CLOSE_PARENTHESIS;
private static final String DROP_LIST_TABLE =
DROP_TABLE_IF_EXISTS + ListPersistenceContract.ListEntry.TABLE_NAME;
private static final String DROP_TASK_TABLE =
DROP_TABLE_IF_EXISTS + TaskPersistenceContract.TaskEntry.TABLE_NAME;
public LocalDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public void onCreate(SQLiteDatabase db) {
// create tables
db.execSQL(CREATE_LIST_TABLE);
db.execSQL(CREATE_TASK_TABLE);
// insert lists
db.execSQL("INSERT INTO " + ListPersistenceContract.ListEntry.TABLE_NAME + " VALUES (1, 'Hobbies')");
db.execSQL("INSERT INTO " + ListPersistenceContract.ListEntry.TABLE_NAME + " VALUES (2, 'Sports')");
// insert sample tasks
db.execSQL("INSERT INTO " + TaskPersistenceContract.TaskEntry.TABLE_NAME + " VALUES (1, 1, 'Play the guitar')");
db.execSQL("INSERT INTO " + TaskPersistenceContract.TaskEntry.TABLE_NAME + " VALUES (2, 1, 'Play video games')");
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d("Task list", "Upgrading db from version "
+ oldVersion + " to " + newVersion);
db.execSQL(LocalDbHelper.DROP_LIST_TABLE);
db.execSQL(LocalDbHelper.DROP_TASK_TABLE);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Not required as at version 1
}
}
3. Create ContentProvider with query, insert, update and delete operations
(in this example, i put a join of tables for a query. You can use the same logic for your insert, update and delete operations.)
public class MyAppContentProvider extends ContentProvider {
private static final int LIST = 100;
private static final int LIST_ITEM = 101;
private static final int LIST_AND_TASK = 102;
private static final int TASK = 200;
private static final int TASK_ITEM = 201;
private static final UriMatcher sUriMatcher = buildUriMatcher();
private LocalDbHelper mLocalDbHelper;
private static final SQLiteQueryBuilder sListAndTask;
static{
sListAndTask = new SQLiteQueryBuilder();
sListAndTask.setTables(
ListPersistenceContract.ListEntry.TABLE_NAME + " INNER JOIN " +
TaskPersistenceContract.TaskEntry.TABLE_NAME +
" ON " + ListPersistenceContract.ListEntry.TABLE_NAME +
"." + ListPersistenceContract.ListEntry._ID +
" = " + TaskPersistenceContract.TaskEntry.TABLE_NAME +
"." + TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID);
}
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = ListPersistenceContract.CONTENT_AUTHORITY;
matcher.addURI(authority, ListPersistenceContract.ListEntry.TABLE_NAME, LIST);
matcher.addURI(authority, ListPersistenceContract.ListEntry.TABLE_NAME + "/*", LIST_ITEM);
matcher.addURI(authority, ListPersistenceContract.ListEntry.LIST_AND_TASK, LIST_AND_TASK);
matcher.addURI(authority, TaskPersistenceContract.TaskEntry.TABLE_NAME, TASK);
matcher.addURI(authority, TaskPersistenceContract.TaskEntry.TABLE_NAME + "/*", TASK_ITEM);
return matcher;
}
@Override
public boolean onCreate() {
mLocalDbHelper = new LocalDbHelper(getContext());
return true;
}
@Nullable
@Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case LIST:
return ListPersistenceContract.CONTENT_LIST_TYPE;
case LIST_ITEM:
return ListPersistenceContract.CONTENT_LIST_ITEM_TYPE;
case TASK:
return TaskPersistenceContract.CONTENT_TASK_TYPE;
case TASK_ITEM:
return TaskPersistenceContract.CONTENT_TASK_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor retCursor;
switch (sUriMatcher.match(uri)) {
case LIST:
retCursor = mLocalDbHelper.getReadableDatabase().query(
ListPersistenceContract.ListEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case LIST_ITEM:
String[] where_list = {uri.getLastPathSegment()};
retCursor = mLocalDbHelper.getReadableDatabase().query(
ListPersistenceContract.ListEntry.TABLE_NAME,
projection,
ListPersistenceContract.ListEntry._ID + " = ?",
where_list,
null,
null,
sortOrder
);
break;
case LIST_AND_TASK:
retCursor = sListAndTask.query(mLocalDbHelper.getReadableDatabase(),
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case TASK:
retCursor = mLocalDbHelper.getReadableDatabase().query(
TaskPersistenceContract.TaskEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case TASK_ITEM:
String[] where_task = {uri.getLastPathSegment()};
retCursor = mLocalDbHelper.getReadableDatabase().query(
TaskPersistenceContract.TaskEntry.TABLE_NAME,
projection,
TaskPersistenceContract.TaskEntry._ID + " = ?",
where_task,
null,
null,
sortOrder
);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
Uri returnUri;
Cursor exists;
switch (match) {
case LIST:
exists = db.query(
ListPersistenceContract.ListEntry.TABLE_NAME,
new String[]{ListPersistenceContract.ListEntry._ID},
ListPersistenceContract.ListEntry._ID + " = ?",
new String[]{values.getAsString(ListPersistenceContract.ListEntry._ID)},
null,
null,
null
);
if (exists.moveToLast()) {
long _id = db.update(
ListPersistenceContract.ListEntry.TABLE_NAME, values,
ListPersistenceContract.ListEntry._ID + " = ?",
new String[]{values.getAsString(ListPersistenceContract.ListEntry._ID)}
);
if (_id > 0) {
returnUri = ListPersistenceContract.ListEntry.buildListUriWith(_id);
} else {
throw new android.database.SQLException("Failed to insert row into " + uri);
}
} else {
long _id = db.insert(ListPersistenceContract.ListEntry.TABLE_NAME, null, values);
if (_id > 0) {
returnUri = ListPersistenceContract.ListEntry.buildListUriWith(_id);
} else {
throw new android.database.SQLException("Failed to insert row into " + uri);
}
}
exists.close();
break;
case TASK:
exists = db.query(
TaskPersistenceContract.TaskEntry.TABLE_NAME,
new String[]{TaskPersistenceContract.TaskEntry._ID},
TaskPersistenceContract.TaskEntry._ID + " = ?",
new String[]{values.getAsString(TaskPersistenceContract.TaskEntry._ID)},
null,
null,
null
);
if (exists.moveToLast()) {
long _id = db.update(
TaskPersistenceContract.TaskEntry.TABLE_NAME, values,
TaskPersistenceContract.TaskEntry._ID + " = ?",
new String[]{values.getAsString(TaskPersistenceContract.TaskEntry._ID)}
);
if (_id > 0) {
returnUri = TaskPersistenceContract.TaskEntry.buildTaskUriWith(_id);
} else {
throw new android.database.SQLException("Failed to insert row into " + uri);
}
} else {
long _id = db.insert(TaskPersistenceContract.TaskEntry.TABLE_NAME, null, values);
if (_id > 0) {
returnUri = TaskPersistenceContract.TaskEntry.buildTaskUriWith(_id);
} else {
throw new android.database.SQLException("Failed to insert row into " + uri);
}
}
exists.close();
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsDeleted;
switch (match) {
case LIST:
rowsDeleted = db.delete(
ListPersistenceContract.ListEntry.TABLE_NAME, selection, selectionArgs);
break;
case TASK:
rowsDeleted = db.delete(
TaskPersistenceContract.TaskEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (selection == null || rowsDeleted != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
int rowsUpdated;
switch (match) {
case LIST:
rowsUpdated = db.update(ListPersistenceContract.ListEntry.TABLE_NAME, values, selection,
selectionArgs
);
break;
case TASK:
rowsUpdated = db.update(TaskPersistenceContract.TaskEntry.TABLE_NAME, values, selection,
selectionArgs
);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (rowsUpdated != 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsUpdated;
}
}