Как я могу разделить длинный одиночный SQLiteOpenHelper на несколько классов, по одному для каждой таблицы

Я знаю, что это было задано пару раз раньше, но во всех этих вопросах ни OP, ни люди, которые ответили, не дали ясных примеров.

Так что я пытаюсь спросить здесь, если у вас есть класс, подобный этому

public class MyDatabaseDB {

    // database constants
    public static final String DB_NAME = "mydatabase.db";
    public static final int    DB_VERSION = 1;

    // list table constants
    public static final String LIST_TABLE = "list";

    public static final String LIST_ID = "_id";
    public static final int    LIST_ID_COL = 0;

    public static final String LIST_NAME = "list_name";
    public static final int    LIST_NAME_COL = 1;

    // task table constants
    public static final String TASK_TABLE = "task";

    public static final String TASK_ID = "_id";
    public static final int    TASK_ID_COL = 0;

    public static final String TASK_LIST_ID = "list_id";
    public static final int    TASK_LIST_ID_COL = 1;

    public static final String TASK_NAME = "task_name";
    public static final int    TASK_NAME_COL = 2; 

    // CREATE and DROP TABLE statements
    public static final String CREATE_LIST_TABLE = 
            "CREATE TABLE " + LIST_TABLE + " (" + 
            LIST_ID   + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 
            LIST_NAME + " TEXT    UNIQUE)";

    public static final String CREATE_TASK_TABLE = 
            "CREATE TABLE " + TASK_TABLE + " (" + 
            TASK_ID         + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 
            TASK_LIST_ID    + " INTEGER, " + 
            TASK_NAME       + " TEXT " + 
           )";

    public static final String DROP_LIST_TABLE = 
            "DROP TABLE IF EXISTS " + LIST_TABLE;

    public static final String DROP_TASK_TABLE = 
            "DROP TABLE IF EXISTS " + TASK_TABLE;

    private static class DBHelper extends SQLiteOpenHelper {

        public DBHelper(Context context, String name, 
                CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            // create tables
            db.execSQL(CREATE_LIST_TABLE);
            db.execSQL(CREATE_TASK_TABLE);

            // insert lists
            db.execSQL("INSERT INTO list VALUES (1, 'Hobbies')");
            db.execSQL("INSERT INTO list VALUES (2, 'Sports')");

            // insert sample tasks
            db.execSQL("INSERT INTO task VALUES (1, 1, 'Play the guitar')");
            db.execSQL("INSERT INTO task VALUES (2, 1, 'Play video games')");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, 
                int oldVersion, int newVersion) {

            Log.d("Task list", "Upgrading db from version " 
                    + oldVersion + " to " + newVersion);


            db.execSQL(MyDatabaseDB.DROP_LIST_TABLE);
            db.execSQL(MyDatabaseDB.DROP_TASK_TABLE);
            onCreate(db);
        }
    }

    // database object and database helper object
    private SQLiteDatabase db;
    private DBHelper dbHelper;

    // constructor
    public MyDatabaseDB(Context context) {
        dbHelper = new DBHelper(context, DB_NAME, null, DB_VERSION);
    }

    // private methods
    private void openReadableDB() {
        db = dbHelper.getReadableDatabase();
    }

    private void openWriteableDB() {
        db = dbHelper.getWritableDatabase();
    }

    private void closeDB() {
        if (db != null)
            db.close();
    }

    // public methods   

    public long insertTask(Task task) {
        ContentValues cv = new ContentValues();
        cv.put(TASK_LIST_ID, task.getListId());
        cv.put(TASK_NAME, task.getName());        

        this.openWriteableDB();
        long rowID = db.insert(TASK_TABLE, null, cv);
        this.closeDB();

        return rowID;
    }    

    public int updateTask(Task task) {
        ContentValues cv = new ContentValues();
        cv.put(TASK_LIST_ID, task.getListId());
        cv.put(TASK_NAME, task.getName());        

        String where = TASK_ID + "= ?";
        String[] whereArgs = { String.valueOf(task.getId()) };

        this.openWriteableDB();
        int rowCount = db.update(TASK_TABLE, cv, where, whereArgs);
        this.closeDB();

        return rowCount;
    }    

    public int deleteTask(long id) {
        String where = TASK_ID + "= ?";
        String[] whereArgs = { String.valueOf(id) };

        this.openWriteableDB();
        int rowCount = db.delete(TASK_TABLE, where, whereArgs);
        this.closeDB();

        return rowCount;
    }
}

Это очень сокращенная версия моего класса, построенная с использованием некоторого кода, который я нашел в режиме онлайн. В этом примере я показываю только код для двух моих таблиц: "Список и задача" и только некоторые из методов sql для таблицы Task: insertTask, updateTask и deleteTask.

Несмотря на то, что код, показанный выше, работает, я не думаю, что было бы неплохо иметь весь код для того, чтобы сказать десять таблиц в одном классе. Поэтому я попытался разбить весь этот код на несколько классов, по одному для каждой таблицы. Что-то вроде этого:

public class MyDatabaseDB {

    // database constants
    public static final String DB_NAME = "mydatabase.db";
    public static final int    DB_VERSION = 1;


    private static class DBHelper extends SQLiteOpenHelper {

        public DBHelper(Context context, String name, 
                CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            // create tables
            db.execSQL(ListDAL.CREATE_LIST_TABLE);
            db.execSQL(TaskDAL.CREATE_TASK_TABLE);

            // insert lists
            db.execSQL("INSERT INTO list VALUES (1, 'Hobbies')");
            db.execSQL("INSERT INTO list VALUES (2, 'Sports')");

            // insert sample tasks
            db.execSQL("INSERT INTO task VALUES (1, 1, 'Play the guitar')");
            db.execSQL("INSERT INTO task VALUES (2, 1, 'Play video games')");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, 
                int oldVersion, int newVersion) {

            Log.d("Task list", "Upgrading db from version " 
                    + oldVersion + " to " + newVersion);


            db.execSQL(ListDAL.DROP_LIST_TABLE);
            db.execSQL(TaskDAL.DROP_TASK_TABLE);
            onCreate(db);
        }
    }

    // database object and database helper object
    private SQLiteDatabase db;
    private DBHelper dbHelper;

    // constructor
    public MyDatabaseDB(Context context) {
        dbHelper = new DBHelper(context, DB_NAME, null, DB_VERSION);
    }

    // private methods
    private void openReadableDB() {
        db = dbHelper.getReadableDatabase();
    }

    private void openWriteableDB() {
        db = dbHelper.getWritableDatabase();
    }

    private void closeDB() {
        if (db != null)
            db.close();
    }   

}

Это два новых класса, которые я создал для привязки кода к определенной таблице:

В ListDAL не много кода

public class ListDAL {

   // list table constants
    public static final String LIST_TABLE = "list";

    public static final String LIST_ID = "_id";
    public static final int    LIST_ID_COL = 0;

    public static final String LIST_NAME = "list_name";
    public static final int    LIST_NAME_COL = 1;

    // CREATE and DROP TABLE statements
    public static final String CREATE_LIST_TABLE = 
            "CREATE TABLE " + LIST_TABLE + " (" + 
            LIST_ID   + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 
            LIST_NAME + " TEXT    UNIQUE)";

    public static final String DROP_LIST_TABLE = 
            "DROP TABLE IF EXISTS " + LIST_TABLE;


}

Класс TaskDAL - это тот, который содержит большую часть кода, и именно в этом классе у меня есть проблемы, особенно в insertTask, updateTask и deleteTask с вызовы вроде this.openWriteableDB(), this.openWriteableDB() или называются db.insert(TASK_TABLE, null, cv).

Поскольку эти методы больше не находятся внутри TaskDAL, я не могу получить к ним доступ. Я попытался передать некоторые ссылки на эти методы, которые будут использоваться вместо этого или db, но это не сработало

public class TaskDAL {
    // task table constants
    public static final String TASK_TABLE = "task";

    public static final String TASK_ID = "_id";
    public static final int    TASK_ID_COL = 0;

    public static final String TASK_LIST_ID = "list_id";
    public static final int    TASK_LIST_ID_COL = 1;

    public static final String TASK_NAME = "task_name";
    public static final int    TASK_NAME_COL = 2; 

    // CREATE and DROP TABLE statements
    public static final String CREATE_TASK_TABLE = 
            "CREATE TABLE " + TASK_TABLE + " (" + 
            TASK_ID         + " INTEGER PRIMARY KEY AUTOINCREMENT, " + 
            TASK_LIST_ID    + " INTEGER, " + 
            TASK_NAME       + " TEXT " + 
           )";
    public static final String DROP_TASK_TABLE = 
            "DROP TABLE IF EXISTS " + TASK_TABLE;       

     // public methods   

    public long insertTask(Task task) {
        ContentValues cv = new ContentValues();
        cv.put(TASK_LIST_ID, task.getListId());
        cv.put(TASK_NAME, task.getName());        

        this.openWriteableDB();
        long rowID = db.insert(TASK_TABLE, null, cv);
        this.closeDB();

        return rowID;
    }    

    public int updateTask(Task task) {
        ContentValues cv = new ContentValues();
        cv.put(TASK_LIST_ID, task.getListId());
        cv.put(TASK_NAME, task.getName());        

        String where = TASK_ID + "= ?";
        String[] whereArgs = { String.valueOf(task.getId()) };

        this.openWriteableDB();
        int rowCount = db.update(TASK_TABLE, cv, where, whereArgs);
        this.closeDB();

        return rowCount;
    }    

    public int deleteTask(long id) {
        String where = TASK_ID + "= ?";
        String[] whereArgs = { String.valueOf(id) };

        this.openWriteableDB();
        int rowCount = db.delete(TASK_TABLE, where, whereArgs);
        this.closeDB();

        return rowCount;
    }
}

Значит, кто-нибудь из вас когда-либо пытался так что-то подобное??? Если бы мне удалось правильно разделить код БД на несколько классов, я бы все еще мог бы СОХРАНИТЬ таблицы?

P.S. Пожалуйста, не закрывайте этот вопрос, если вы думаете, что я сделал что-то неправильно, скажите мне, и я постараюсь его исправить.

Ответ 1

Как вы думаете, вы могли бы дать несколько советов о том, как это сделать.

Это не имеет ничего общего с Android, и даже не так много общего с Java. Разложение длинных структур программирования (например, классов в Java) на более мелкие структуры имеет стандартные методы, называемые шаблонами проектирования, с языковыми реализациями.

Например, вы можете пойти с составной шаблон:

  • Определите интерфейс - я назову его TableHelper здесь - который имеет методы onCreate() и onUpdate(), которые соответствуют тем, которые находятся на SQLiteOpenHelper

  • Определите N классов, по одному на таблицу, которые реализуют интерфейс TableHelper и предоставляют логику создания и обновления для этой таблицы (наряду с любой другой бизнес-логикой, которую вы хотите иметь в этих классах)

  • Попросите SQLiteOpenHelper указать TableHelper[] содержащие экземпляры ваших классов TableHelper и делегировать onCreate() и onUpgrade() этим TableHelper экземплярам путем итерации по массиву

Ответ 2

Используя Content Provider, вы можете разделить длинный SQLiteOpenHelper в нескольких файлах: один persistenceContract для каждой таблицы, один маленький SQLiteOpenHelper и длинный и многостраничный ContentProvider.

С помощью этого решения вам нужно написать больше кода. Но это более читаемо и легко поддерживать.


Преимущества:

  • Легко назвать столбец таблицы при написании вашего запроса (пример: TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME)
  • Легко читается и поддерживается
  • Меньше ошибок опечатки
  • Использовать курсор и логику CursorLoader

Недостаток:

  • Более подробные

Попробуйте с исходным кодом!

1. Создайте PersistenceContract для каждой таблицы

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. Создать 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. Создайте ContentProvider с помощью операций запроса, вставки, обновления и удаления.

(в этом примере я помещаю объединение таблиц для запроса. Вы можете использовать ту же логику для операций вставки, обновления и удаления.)

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;
    }
}