Use loader to query for a cursor for the list adapter
This commit is contained in:
parent
11c4be68b9
commit
7d0718387f
@ -0,0 +1,68 @@
|
|||||||
|
package com.philliphsu.clock2.alarms;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||||
|
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 6/29/2016.
|
||||||
|
*
|
||||||
|
* TODO: Make this abstract for other data types.
|
||||||
|
*/
|
||||||
|
public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
|
||||||
|
private static final String TAG = "AlarmsCursorAdapter";
|
||||||
|
|
||||||
|
private final OnListItemInteractionListener<Alarm> mListener;
|
||||||
|
private AlarmCursor mCursor;
|
||||||
|
|
||||||
|
public AlarmsCursorAdapter(OnListItemInteractionListener<Alarm> listener) {
|
||||||
|
mListener = listener;
|
||||||
|
setHasStableIds(true); // TODO: why do we need this?
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlarmViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
return new AlarmViewHolder(parent, mListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(AlarmViewHolder holder, int position) {
|
||||||
|
if (!mCursor.moveToPosition(position)) {
|
||||||
|
Log.e(TAG, "Failed to bind alarm " + position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
holder.onBind(mCursor.getAlarm());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mCursor == null ? 0 : mCursor.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
if (mCursor == null || !mCursor.moveToPosition(position)) {
|
||||||
|
return super.getItemId(position); // -1
|
||||||
|
}
|
||||||
|
return mCursor.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cursor param should be the appropriate subclass?
|
||||||
|
public void swapCursor(Cursor cursor) {
|
||||||
|
if (mCursor == cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mCursor != null) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
mCursor = (AlarmCursor) cursor;
|
||||||
|
// There is no way to notify the adapter of individual
|
||||||
|
// item changes when the dataset is a cursor.
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
package com.philliphsu.clock2.alarms;
|
package com.philliphsu.clock2.alarms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -12,6 +14,7 @@ import android.view.ViewGroup;
|
|||||||
import com.philliphsu.clock2.Alarm;
|
import com.philliphsu.clock2.Alarm;
|
||||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||||
import com.philliphsu.clock2.R;
|
import com.philliphsu.clock2.R;
|
||||||
|
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
|
||||||
import com.philliphsu.clock2.model.BaseRepository;
|
import com.philliphsu.clock2.model.BaseRepository;
|
||||||
import com.philliphsu.clock2.model.DatabaseManager;
|
import com.philliphsu.clock2.model.DatabaseManager;
|
||||||
|
|
||||||
@ -24,10 +27,15 @@ import butterknife.ButterKnife;
|
|||||||
* Activities containing this fragment MUST implement the {@link OnAlarmInteractionListener}
|
* Activities containing this fragment MUST implement the {@link OnAlarmInteractionListener}
|
||||||
* interface.
|
* interface.
|
||||||
*/
|
*/
|
||||||
public class AlarmsFragment extends Fragment implements BaseRepository.DataObserver<Alarm> {
|
// TODO: Use native fragments since we're targeting API >=19?
|
||||||
|
// TODO: Use native LoaderCallbacks.
|
||||||
|
public class AlarmsFragment extends Fragment implements
|
||||||
|
BaseRepository.DataObserver<Alarm>, LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
private OnAlarmInteractionListener mListener;
|
private OnAlarmInteractionListener mListener;
|
||||||
|
@Deprecated
|
||||||
private AlarmsAdapter mAdapter;
|
private AlarmsAdapter mAdapter;
|
||||||
|
private AlarmsCursorAdapter mCursorAdapter;
|
||||||
private DatabaseManager mDatabaseManager;
|
private DatabaseManager mDatabaseManager;
|
||||||
|
|
||||||
@Bind(R.id.list) RecyclerView mList;
|
@Bind(R.id.list) RecyclerView mList;
|
||||||
@ -56,6 +64,8 @@ public class AlarmsFragment extends Fragment implements BaseRepository.DataObser
|
|||||||
// TODO Read arguments
|
// TODO Read arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the loader to load the list of runs
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
mDatabaseManager = DatabaseManager.getInstance(getActivity());
|
mDatabaseManager = DatabaseManager.getInstance(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +77,12 @@ public class AlarmsFragment extends Fragment implements BaseRepository.DataObser
|
|||||||
// Set the adapter
|
// Set the adapter
|
||||||
Context context = view.getContext();
|
Context context = view.getContext();
|
||||||
mList.setLayoutManager(new LinearLayoutManager(context));
|
mList.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
// TODO: Create a new adapter subclass with constructor that
|
||||||
|
// has no dataset param. The Loader will set the Cursor after it
|
||||||
|
// has finished loading it.
|
||||||
mAdapter = new AlarmsAdapter(mDatabaseManager.getAlarms(), mListener);
|
mAdapter = new AlarmsAdapter(mDatabaseManager.getAlarms(), mListener);
|
||||||
mList.setAdapter(mAdapter);
|
mCursorAdapter = new AlarmsCursorAdapter(mListener);
|
||||||
|
mList.setAdapter(mCursorAdapter);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +98,7 @@ public class AlarmsFragment extends Fragment implements BaseRepository.DataObser
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
// TODO: Need to refresh the list's adapter for any item changes. Consider doing this in
|
// TODO: Need to refresh the list's adapter for any item changes. Consider doing this in
|
||||||
// onNewActivity().
|
// onNewActivity().
|
||||||
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -125,6 +140,23 @@ public class AlarmsFragment extends Fragment implements BaseRepository.DataObser
|
|||||||
mAdapter.updateItem(oldItem, newItem);
|
mAdapter.updateItem(oldItem, newItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public android.support.v4.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new AlarmsListCursorLoader(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(android.support.v4.content.Loader<Cursor> loader, Cursor data) {
|
||||||
|
// Called on the main thread after loading is complete
|
||||||
|
mCursorAdapter.swapCursor(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
|
||||||
|
// The adapter's current cursor should not be used anymore
|
||||||
|
mCursorAdapter.swapCursor(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface must be implemented by activities that contain this
|
* This interface must be implemented by activities that contain this
|
||||||
* fragment to allow an interaction in this fragment to be communicated
|
* fragment to allow an interaction in this fragment to be communicated
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import android.database.Cursor;
|
|||||||
import android.database.CursorWrapper;
|
import android.database.CursorWrapper;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.philliphsu.clock2.Alarm;
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
// TODO: Consider creating an inner class that implements BaseColumns
|
// TODO: Consider creating an inner class that implements BaseColumns
|
||||||
// and defines all the columns.
|
// and defines all the columns.
|
||||||
// TODO: Consider statically defining index constants for each column,
|
// TODO: Consider defining index constants for each column,
|
||||||
// and then removing all cursor getColumnIndex() calls.
|
// and then removing all cursor getColumnIndex() calls.
|
||||||
private static final String TABLE_ALARMS = "alarms";
|
private static final String TABLE_ALARMS = "alarms";
|
||||||
private static final String COLUMN_ID = "_id";
|
private static final String COLUMN_ID = "_id";
|
||||||
@ -39,13 +40,8 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
private static final String COLUMN_RINGTONE = "ringtone";
|
private static final String COLUMN_RINGTONE = "ringtone";
|
||||||
private static final String COLUMN_VIBRATES = "vibrates";
|
private static final String COLUMN_VIBRATES = "vibrates";
|
||||||
private static final String COLUMN_ENABLED = "enabled";
|
private static final String COLUMN_ENABLED = "enabled";
|
||||||
|
private static final String COLUMN_RING_TIME_MILLIS = "ring_time_millis";
|
||||||
private static final String COLUMN_SNOOZING_UNTIL_MILLIS = "snoozing_until_millis";
|
private static final String COLUMN_SNOOZING_UNTIL_MILLIS = "snoozing_until_millis";
|
||||||
|
|
||||||
// TODO: Consider creating an inner class that implements BaseColumns
|
|
||||||
// and defines all the columns.
|
|
||||||
private static final String TABLE_ALARM_RECURRING_DAYS = "alarm_recurring_days";
|
|
||||||
// TODO: change value to _id if changing this to a primary auto-incrementing key
|
|
||||||
private static final String COLUMN_ALARM_ID = "alarm_id";
|
|
||||||
private static final String COLUMN_SUNDAY = "sunday";
|
private static final String COLUMN_SUNDAY = "sunday";
|
||||||
private static final String COLUMN_MONDAY = "monday";
|
private static final String COLUMN_MONDAY = "monday";
|
||||||
private static final String COLUMN_TUESDAY = "tuesday";
|
private static final String COLUMN_TUESDAY = "tuesday";
|
||||||
@ -54,42 +50,18 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
private static final String COLUMN_FRIDAY = "friday";
|
private static final String COLUMN_FRIDAY = "friday";
|
||||||
private static final String COLUMN_SATURDAY = "saturday";
|
private static final String COLUMN_SATURDAY = "saturday";
|
||||||
|
|
||||||
// Statically define column indices for the days so we don't have
|
// https://www.sqlite.org/lang_select.html#orderby
|
||||||
// to call cursor.getColumnIndex(COLUMN_[DAY]). We offset the value
|
// Rows are first sorted based on the results of evaluating the left-most expression in the
|
||||||
// by one because COLUMN_ALARM_ID is actually first.
|
// ORDER BY list, then ties are broken by evaluating the second left-most expression and so on.
|
||||||
private static final int INDEX_SUNDAY = SUNDAY + 1;
|
// The order in which two rows for which all ORDER BY expressions evaluate to equal values are
|
||||||
private static final int INDEX_MONDAY = MONDAY + 1;
|
// returned is undefined. Each ORDER BY expression may be optionally followed by one of the keywords
|
||||||
private static final int INDEX_TUESDAY = TUESDAY + 1;
|
// ASC (smaller values are returned first) or DESC (larger values are returned first). If neither
|
||||||
private static final int INDEX_WEDNESDAY = WEDNESDAY + 1;
|
// ASC or DESC are specified, rows are sorted in ascending (smaller values first) order by default.
|
||||||
private static final int INDEX_THURSDAY = THURSDAY + 1;
|
|
||||||
private static final int INDEX_FRIDAY = FRIDAY + 1;
|
|
||||||
private static final int INDEX_SATURDAY = SATURDAY + 1;
|
|
||||||
|
|
||||||
private static void createAlarmsTable(SQLiteDatabase db) {
|
// First sort by ring time in ascending order (smaller values first),
|
||||||
db.execSQL("CREATE TABLE " + TABLE_ALARMS + " ("
|
// then break ties by sorting by id in ascending order.
|
||||||
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
private static final String SORT_ORDER =
|
||||||
+ COLUMN_HOUR + " INTEGER NOT NULL, "
|
COLUMN_RING_TIME_MILLIS + " ASC, " + COLUMN_ID + " ASC";
|
||||||
+ COLUMN_MINUTES + " INTEGER NOT NULL, "
|
|
||||||
+ COLUMN_LABEL + " TEXT, "
|
|
||||||
+ COLUMN_RINGTONE + " TEXT NOT NULL, "
|
|
||||||
+ COLUMN_VIBRATES + " INTEGER NOT NULL, "
|
|
||||||
+ COLUMN_ENABLED + " INTEGER NOT NULL, "
|
|
||||||
+ COLUMN_SNOOZING_UNTIL_MILLIS + " INTEGER);");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void createRecurringDaysTable(SQLiteDatabase db) {
|
|
||||||
db.execSQL("CREATE TABLE " + TABLE_ALARM_RECURRING_DAYS + " ("
|
|
||||||
// TODO: The ID can instead be a primary auto-incrementing key, since a recurrence entry
|
|
||||||
// will always be made in conjunction with an alarm entry.
|
|
||||||
+ COLUMN_ALARM_ID + " INTEGER REFERENCES " + TABLE_ALARMS + "(" + COLUMN_ID + "), "
|
|
||||||
+ COLUMN_SUNDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_MONDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_TUESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_WEDNESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_THURSDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_FRIDAY + " INTEGER NOT NULL DEFAULT 0, "
|
|
||||||
+ COLUMN_SATURDAY + " INTEGER NOT NULL DEFAULT 0);");
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlarmDatabaseHelper(Context context) {
|
public AlarmDatabaseHelper(Context context) {
|
||||||
super(context, DB_NAME, null, VERSION_1);
|
super(context, DB_NAME, null, VERSION_1);
|
||||||
@ -102,8 +74,23 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
// of the value. As soon as INTEGER values are read off of disk and into memory for processing,
|
// of the value. As soon as INTEGER values are read off of disk and into memory for processing,
|
||||||
// they are converted to the most general datatype (8-byte signed integer).
|
// they are converted to the most general datatype (8-byte signed integer).
|
||||||
// 8 byte == 64 bits so this means they are read as longs...?
|
// 8 byte == 64 bits so this means they are read as longs...?
|
||||||
createAlarmsTable(db);
|
db.execSQL("CREATE TABLE " + TABLE_ALARMS + " ("
|
||||||
createRecurringDaysTable(db);
|
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
|
+ COLUMN_HOUR + " INTEGER NOT NULL, "
|
||||||
|
+ COLUMN_MINUTES + " INTEGER NOT NULL, "
|
||||||
|
+ COLUMN_LABEL + " TEXT, "
|
||||||
|
+ COLUMN_RINGTONE + " TEXT NOT NULL, "
|
||||||
|
+ COLUMN_VIBRATES + " INTEGER NOT NULL, "
|
||||||
|
+ COLUMN_ENABLED + " INTEGER NOT NULL, "
|
||||||
|
+ COLUMN_RING_TIME_MILLIS + " INTEGER NOT NULL, "
|
||||||
|
+ COLUMN_SNOOZING_UNTIL_MILLIS + " INTEGER, "
|
||||||
|
+ COLUMN_SUNDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_MONDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_TUESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_WEDNESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_THURSDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_FRIDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||||
|
+ COLUMN_SATURDAY + " INTEGER NOT NULL DEFAULT 0);");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,12 +99,10 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
// to the schema in the new version.
|
// to the schema in the new version.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider changing param to Alarm.Builder so the Alarm that will be
|
|
||||||
// built can have its ID set.
|
|
||||||
public long insertAlarm(Alarm alarm) {
|
public long insertAlarm(Alarm alarm) {
|
||||||
long id = getWritableDatabase().insert(TABLE_ALARMS, null, toContentValues(alarm));
|
long id = getWritableDatabase().insert(TABLE_ALARMS,
|
||||||
|
null, toContentValues(alarm));
|
||||||
alarm.setId(id);
|
alarm.setId(id);
|
||||||
getWritableDatabase().insert(TABLE_ALARM_RECURRING_DAYS, null, toRecurrenceContentValues(id, alarm));
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,56 +113,30 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
|
public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
|
||||||
newAlarm.setId(oldAlarm.id());
|
newAlarm.setId(oldAlarm.id());
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
int rowsUpdatedInAlarmsTable = db.update(TABLE_ALARMS,
|
return db.update(TABLE_ALARMS,
|
||||||
toContentValues(newAlarm),
|
toContentValues(newAlarm),
|
||||||
COLUMN_ID + " = " + newAlarm.id(),
|
COLUMN_ID + " = " + newAlarm.id(),
|
||||||
null);
|
null);
|
||||||
int rowsUpdatedInRecurrencesTable = db.update(TABLE_ALARM_RECURRING_DAYS,
|
|
||||||
toRecurrenceContentValues(newAlarm.id(), newAlarm),
|
|
||||||
COLUMN_ALARM_ID + " = " + newAlarm.id(),
|
|
||||||
null);
|
|
||||||
if (rowsUpdatedInAlarmsTable == rowsUpdatedInRecurrencesTable && rowsUpdatedInAlarmsTable == 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("rows updated in TABLE_ALARMS = " + rowsUpdatedInAlarmsTable +
|
|
||||||
", rows updated in TABLE_ALARM_RECURRING_DAYS = " + rowsUpdatedInRecurrencesTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int updateAlarm(long id, Alarm newAlarm) {
|
public int updateAlarm(long id, Alarm newAlarm) {
|
||||||
newAlarm.setId(id);
|
newAlarm.setId(id);
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
int rowsUpdatedInAlarmsTable = db.update(TABLE_ALARMS,
|
return db.update(TABLE_ALARMS,
|
||||||
toContentValues(newAlarm),
|
toContentValues(newAlarm),
|
||||||
COLUMN_ID + " = " + id,
|
COLUMN_ID + " = " + id,
|
||||||
null);
|
null);
|
||||||
int rowsUpdatedInRecurrencesTable = db.update(TABLE_ALARM_RECURRING_DAYS,
|
|
||||||
toRecurrenceContentValues(id, newAlarm),
|
|
||||||
COLUMN_ALARM_ID + " = " + id,
|
|
||||||
null);
|
|
||||||
if (rowsUpdatedInAlarmsTable == rowsUpdatedInRecurrencesTable && rowsUpdatedInAlarmsTable == 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("rows updated in TABLE_ALARMS = " + rowsUpdatedInAlarmsTable +
|
|
||||||
", rows updated in TABLE_ALARM_RECURRING_DAYS = " + rowsUpdatedInRecurrencesTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int deleteAlarm(Alarm alarm) {
|
public int deleteAlarm(Alarm alarm) {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
int rowsDeletedInAlarmsTable = db.delete(TABLE_ALARMS,
|
return db.delete(TABLE_ALARMS,
|
||||||
COLUMN_ID + " = " + alarm.id(),
|
COLUMN_ID + " = " + alarm.id(),
|
||||||
null);
|
null);
|
||||||
int rowsDeletedInRecurrencesTable = db.delete(TABLE_ALARM_RECURRING_DAYS,
|
|
||||||
COLUMN_ALARM_ID + " = " + alarm.id(),
|
|
||||||
null);
|
|
||||||
if (rowsDeletedInAlarmsTable == rowsDeletedInRecurrencesTable && rowsDeletedInAlarmsTable == 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("rows deleted in TABLE_ALARMS = " + rowsDeletedInAlarmsTable +
|
|
||||||
", rows deleted in TABLE_ALARM_RECURRING_DAYS = " + rowsDeletedInRecurrencesTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlarmCursor queryAlarm(long id) {
|
public AlarmCursor queryAlarm(long id) {
|
||||||
Cursor alarmCursor = getReadableDatabase().query(TABLE_ALARMS,
|
Cursor c = getReadableDatabase().query(TABLE_ALARMS,
|
||||||
null, // All columns
|
null, // All columns
|
||||||
COLUMN_ID + " = " + id, // Selection for this alarm id
|
COLUMN_ID + " = " + id, // Selection for this alarm id
|
||||||
null, // selection args, none b/c id already specified in selection
|
null, // selection args, none b/c id already specified in selection
|
||||||
@ -185,24 +144,14 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
null, // order/sort by
|
null, // order/sort by
|
||||||
null, // having
|
null, // having
|
||||||
"1"); // limit 1 row
|
"1"); // limit 1 row
|
||||||
Cursor recurrenceCursor = getReadableDatabase().query(TABLE_ALARM_RECURRING_DAYS,
|
return new AlarmCursor(c);
|
||||||
null, // All columns
|
|
||||||
COLUMN_ALARM_ID + " = " + id, // selection
|
|
||||||
null, // selection args
|
|
||||||
null, // group by
|
|
||||||
null, // order by
|
|
||||||
null, // having
|
|
||||||
"1");
|
|
||||||
return new AlarmCursor(alarmCursor, recurrenceCursor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlarmCursor queryAlarms() {
|
public AlarmCursor queryAlarms() {
|
||||||
// Select all rows and columns from both tables
|
// Select all rows and columns
|
||||||
Cursor alarmCursor = getReadableDatabase().query(TABLE_ALARMS,
|
Cursor c = getReadableDatabase().query(TABLE_ALARMS,
|
||||||
null, null, null, null, null, null);
|
null, null, null, null, null, SORT_ORDER);
|
||||||
Cursor recurrenceCursor = getReadableDatabase().query(TABLE_ALARM_RECURRING_DAYS,
|
return new AlarmCursor(c);
|
||||||
null, null, null, null, null, null);
|
|
||||||
return new AlarmCursor(alarmCursor, recurrenceCursor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentValues toContentValues(Alarm alarm) {
|
private ContentValues toContentValues(Alarm alarm) {
|
||||||
@ -213,15 +162,8 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
values.put(COLUMN_RINGTONE, alarm.ringtone());
|
values.put(COLUMN_RINGTONE, alarm.ringtone());
|
||||||
values.put(COLUMN_VIBRATES, alarm.vibrates());
|
values.put(COLUMN_VIBRATES, alarm.vibrates());
|
||||||
values.put(COLUMN_ENABLED, alarm.isEnabled());
|
values.put(COLUMN_ENABLED, alarm.isEnabled());
|
||||||
|
values.put(COLUMN_RING_TIME_MILLIS, alarm.ringsAt());
|
||||||
values.put(COLUMN_SNOOZING_UNTIL_MILLIS, alarm.snoozingUntil());
|
values.put(COLUMN_SNOOZING_UNTIL_MILLIS, alarm.snoozingUntil());
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even though the current impl of insertAlarm() sets the given id on the alarm, we require it
|
|
||||||
// as a param just as an extra measure, in case you happen to not set it.
|
|
||||||
private ContentValues toRecurrenceContentValues(long id, Alarm alarm) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_ALARM_ID, id); // *could* have used alarm.id() instead
|
|
||||||
values.put(COLUMN_SUNDAY, alarm.isRecurring(SUNDAY));
|
values.put(COLUMN_SUNDAY, alarm.isRecurring(SUNDAY));
|
||||||
values.put(COLUMN_MONDAY, alarm.isRecurring(MONDAY));
|
values.put(COLUMN_MONDAY, alarm.isRecurring(MONDAY));
|
||||||
values.put(COLUMN_TUESDAY, alarm.isRecurring(TUESDAY));
|
values.put(COLUMN_TUESDAY, alarm.isRecurring(TUESDAY));
|
||||||
@ -232,13 +174,17 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An alternative method to creating an Alarm from a cursor is to
|
||||||
|
// make an Alarm constructor that takes an Cursor param. However,
|
||||||
|
// this method has the advantage of keeping all the constants
|
||||||
|
// contained within this file. Another advantage is the contents of
|
||||||
|
// the Alarm class remain as pure Java, which can facilitate unit testing
|
||||||
|
// because it has no dependence on Cursor, which is part of the Android
|
||||||
|
// SDK.
|
||||||
public static class AlarmCursor extends CursorWrapper {
|
public static class AlarmCursor extends CursorWrapper {
|
||||||
|
|
||||||
private final Cursor mRecurrenceCursor;
|
public AlarmCursor(Cursor c) {
|
||||||
|
super(c);
|
||||||
public AlarmCursor(Cursor alarmCursor, Cursor recurrenceCursor) {
|
|
||||||
super(alarmCursor);
|
|
||||||
mRecurrenceCursor = recurrenceCursor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,34 +194,37 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
public Alarm getAlarm() {
|
public Alarm getAlarm() {
|
||||||
if (isBeforeFirst() || isAfterLast())
|
if (isBeforeFirst() || isAfterLast())
|
||||||
return null;
|
return null;
|
||||||
|
// TODO: Use getColumnIndexOrThrow()
|
||||||
Alarm alarm = Alarm.builder()
|
Alarm alarm = Alarm.builder()
|
||||||
.hour(getInt(getColumnIndex(COLUMN_HOUR)))
|
.hour(getInt(getColumnIndex(COLUMN_HOUR)))
|
||||||
.minutes(getInt(getColumnIndex(COLUMN_MINUTES)))
|
.minutes(getInt(getColumnIndex(COLUMN_MINUTES)))
|
||||||
.vibrates(getInt(getColumnIndex(COLUMN_VIBRATES)) == 1)
|
.vibrates(isTrue(COLUMN_VIBRATES))
|
||||||
.ringtone(getString(getColumnIndex(COLUMN_RINGTONE)))
|
.ringtone(getString(getColumnIndex(COLUMN_RINGTONE)))
|
||||||
.label(getString(getColumnIndex(COLUMN_LABEL)))
|
.label(getString(getColumnIndex(COLUMN_LABEL)))
|
||||||
.build();
|
.build();
|
||||||
alarm.setId(getLong(getColumnIndex(COLUMN_ID)));
|
alarm.setId(getLong(getColumnIndex(COLUMN_ID)));
|
||||||
alarm.setEnabled(getInt(getColumnIndex(COLUMN_ENABLED)) == 1);
|
alarm.setEnabled(isTrue(COLUMN_ENABLED));
|
||||||
alarm.setSnoozing(getLong(getColumnIndex(COLUMN_SNOOZING_UNTIL_MILLIS)));
|
alarm.setSnoozing(getLong(getColumnIndex(COLUMN_SNOOZING_UNTIL_MILLIS)));
|
||||||
// DatabaseManager moves the primary cursor for you. However, you are responsible
|
alarm.setRecurring(SUNDAY, isTrue(COLUMN_SUNDAY));
|
||||||
// for moving the recurrence cursor yourself, because it is private to this class.
|
alarm.setRecurring(MONDAY, isTrue(COLUMN_MONDAY));
|
||||||
if (mRecurrenceCursor.moveToNext()) {
|
alarm.setRecurring(TUESDAY, isTrue(COLUMN_TUESDAY));
|
||||||
alarm.setRecurring(SUNDAY, isRecurring(INDEX_SUNDAY));
|
alarm.setRecurring(WEDNESDAY, isTrue(COLUMN_WEDNESDAY));
|
||||||
alarm.setRecurring(MONDAY, isRecurring(INDEX_MONDAY));
|
alarm.setRecurring(THURSDAY, isTrue(COLUMN_THURSDAY));
|
||||||
alarm.setRecurring(TUESDAY, isRecurring(INDEX_TUESDAY));
|
alarm.setRecurring(FRIDAY, isTrue(COLUMN_FRIDAY));
|
||||||
alarm.setRecurring(WEDNESDAY, isRecurring(INDEX_WEDNESDAY));
|
alarm.setRecurring(SATURDAY, isTrue(COLUMN_SATURDAY));
|
||||||
alarm.setRecurring(THURSDAY, isRecurring(INDEX_THURSDAY));
|
|
||||||
alarm.setRecurring(FRIDAY, isRecurring(INDEX_FRIDAY));
|
|
||||||
alarm.setRecurring(SATURDAY, isRecurring(INDEX_SATURDAY));
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("No entry in recurrence table for alarm: " + alarm);
|
|
||||||
}
|
|
||||||
return alarm;
|
return alarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
if (isBeforeFirst() || isAfterLast()) {
|
||||||
|
Log.e(TAG, "Failed to retrieve id, cursor out of range");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return getLong(getColumnIndexOrThrow(COLUMN_ID));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isRecurring(int dayColumnIndex) {
|
private boolean isTrue(String columnName) {
|
||||||
return mRecurrenceCursor.getInt(dayColumnIndex) == 1;
|
return getInt(getColumnIndex(columnName)) == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 6/28/2016.
|
||||||
|
*/
|
||||||
|
public class AlarmsListCursorLoader extends SQLiteCursorLoader {
|
||||||
|
|
||||||
|
public AlarmsListCursorLoader(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Cursor loadCursor() {
|
||||||
|
return DatabaseManager.getInstance(getContext()).queryAlarms();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package com.philliphsu.clock2.model;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.philliphsu.clock2.Alarm;
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@ -34,7 +35,8 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #updateAlarm(long, Alarm)} instead
|
* @deprecated Use {@link #updateAlarm(long, Alarm)} instead, because all
|
||||||
|
* that is needed from the oldAlarm is its id.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
|
public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
|
||||||
@ -52,7 +54,7 @@ public class DatabaseManager {
|
|||||||
// Since the query returns at most one row, just return the Alarm the row represents.
|
// Since the query returns at most one row, just return the Alarm the row represents.
|
||||||
public Alarm getAlarm(long id) {
|
public Alarm getAlarm(long id) {
|
||||||
Alarm alarm = null;
|
Alarm alarm = null;
|
||||||
AlarmDatabaseHelper.AlarmCursor cursor = mHelper.queryAlarm(id);
|
AlarmCursor cursor = mHelper.queryAlarm(id);
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
alarm = cursor.getAlarm();
|
alarm = cursor.getAlarm();
|
||||||
cursor.close();
|
cursor.close();
|
||||||
@ -60,9 +62,11 @@ public class DatabaseManager {
|
|||||||
return alarm;
|
return alarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use {@link #queryAlarms()} */
|
||||||
|
@Deprecated
|
||||||
public ArrayList<Alarm> getAlarms() {
|
public ArrayList<Alarm> getAlarms() {
|
||||||
ArrayList<Alarm> alarms = new ArrayList<>();
|
ArrayList<Alarm> alarms = new ArrayList<>();
|
||||||
AlarmDatabaseHelper.AlarmCursor cursor = mHelper.queryAlarms();
|
AlarmCursor cursor = mHelper.queryAlarms();
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
alarms.add(cursor.getAlarm());
|
alarms.add(cursor.getAlarm());
|
||||||
@ -71,4 +75,8 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
return alarms;
|
return alarms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AlarmCursor queryAlarms() {
|
||||||
|
return mHelper.queryAlarms();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 6/28/2016.
|
||||||
|
*
|
||||||
|
* Efficiently loads and holds a Cursor.
|
||||||
|
*/
|
||||||
|
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
|
||||||
|
private Cursor mCursor;
|
||||||
|
|
||||||
|
public SQLiteCursorLoader(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Cursor loadCursor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor loadInBackground() {
|
||||||
|
Cursor cursor = loadCursor();
|
||||||
|
if (cursor != null) {
|
||||||
|
// Ensure that the content window is filled
|
||||||
|
// Ensure that the data is available in memory once it is
|
||||||
|
// passed to the main thread
|
||||||
|
cursor.getCount();
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(Cursor data) {
|
||||||
|
Cursor oldCursor = mCursor;
|
||||||
|
mCursor = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the old cursor because it is no longer needed.
|
||||||
|
// Because an existing cursor may be cached and redelivered, it is important
|
||||||
|
// to make sure that the old cursor and the new cursor are not the
|
||||||
|
// same before the old cursor is closed.
|
||||||
|
if (oldCursor != null && oldCursor != data && !oldCursor.isClosed()) {
|
||||||
|
oldCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refer to the docs if you wish to understand the rest of the API as used below.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mCursor != null) {
|
||||||
|
deliverResult(mCursor);
|
||||||
|
}
|
||||||
|
if (takeContentChanged() || mCursor == null) {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// Attempt to cancel the current load task if possible.
|
||||||
|
cancelLoad();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onCanceled(Cursor cursor) {
|
||||||
|
if (cursor != null && !cursor.isClosed()) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
super.onReset();
|
||||||
|
// Ensure the loader is stopped
|
||||||
|
onStopLoading();
|
||||||
|
if (mCursor != null && !mCursor.isClosed()) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
mCursor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user