Wrote Loader for List of Alarms
This commit is contained in:
parent
b1657c221e
commit
4eec10f86a
@ -26,6 +26,7 @@ public class PendingAlarmScheduler extends BroadcastReceiver {
|
|||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
throw new IllegalStateException("No alarm id received");
|
throw new IllegalStateException("No alarm id received");
|
||||||
}
|
}
|
||||||
|
// TODO: Do this in the background. AsyncTask?
|
||||||
Alarm alarm = checkNotNull(DatabaseManager.getInstance(context).getAlarm(id));
|
Alarm alarm = checkNotNull(DatabaseManager.getInstance(context).getAlarm(id));
|
||||||
AlarmUtils.scheduleAlarm(context, alarm, false);
|
AlarmUtils.scheduleAlarm(context, alarm, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.philliphsu.clock2;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 7/1/2016.
|
||||||
|
*/
|
||||||
|
public final class RecyclerViewItemChangeHandler<T> {
|
||||||
|
|
||||||
|
private final RecyclerView mRecyclerView;
|
||||||
|
private final View mSnackbarAnchor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recyclerView the RecyclerView for which we should handle item change events
|
||||||
|
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
|
||||||
|
*/
|
||||||
|
public RecyclerViewItemChangeHandler(RecyclerView recyclerView, View snackbarAnchor) {
|
||||||
|
mRecyclerView = recyclerView;
|
||||||
|
mSnackbarAnchor = snackbarAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an item change event to the item in the
|
||||||
|
* RecyclerView with the given stable id.
|
||||||
|
*/
|
||||||
|
// This won't work if the change on the item would cause it
|
||||||
|
// to be sorted in a different position!
|
||||||
|
public void notifyItemChanged(long id) {
|
||||||
|
if (id < 0) throw new IllegalArgumentException("id < 0");
|
||||||
|
int position = mRecyclerView.findViewHolderForItemId(id).getAdapterPosition();
|
||||||
|
mRecyclerView.getAdapter().notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyItemRemoved(long id) {
|
||||||
|
if (id < 0) throw new IllegalArgumentException("id < 0");
|
||||||
|
int position = mRecyclerView.findViewHolderForItemId(id).getAdapterPosition();
|
||||||
|
mRecyclerView.getAdapter().notifyItemRemoved(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,11 +3,11 @@ package com.philliphsu.clock2.alarms;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
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.util.Log;
|
import android.util.Log;
|
||||||
@ -19,23 +19,25 @@ 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.editalarm.EditAlarmActivity;
|
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
|
||||||
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
|
import com.philliphsu.clock2.model.AlarmListLoader;
|
||||||
import com.philliphsu.clock2.model.DatabaseManager;
|
import com.philliphsu.clock2.model.DatabaseManager;
|
||||||
import com.philliphsu.clock2.util.AlarmUtils;
|
import com.philliphsu.clock2.util.AlarmUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
// TODO: Use native fragments since we're targeting API >=19?
|
// TODO: Use native fragments since we're targeting API >=19?
|
||||||
// TODO: Use native LoaderCallbacks.
|
// TODO: Use native LoaderCallbacks.
|
||||||
public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Alarm>>,
|
||||||
OnListItemInteractionListener<Alarm> {
|
OnListItemInteractionListener<Alarm> {
|
||||||
private static final int REQUEST_EDIT_ALARM = 0;
|
private static final int REQUEST_EDIT_ALARM = 0;
|
||||||
public static final int REQUEST_CREATE_ALARM = 1;
|
public static final int REQUEST_CREATE_ALARM = 1;
|
||||||
private static final String TAG = "AlarmsFragment";
|
private static final String TAG = "AlarmsFragment";
|
||||||
|
|
||||||
private AlarmsCursorAdapter mCursorAdapter;
|
private AlarmsAdapter mAdapter;
|
||||||
private DatabaseManager mDatabaseManager;
|
|
||||||
|
|
||||||
@Bind(R.id.list) RecyclerView mList;
|
@Bind(R.id.list) RecyclerView mList;
|
||||||
|
|
||||||
@ -65,7 +67,6 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
|||||||
|
|
||||||
// Initialize the loader to load the list of runs
|
// Initialize the loader to load the list of runs
|
||||||
getLoaderManager().initLoader(0, null, this);
|
getLoaderManager().initLoader(0, null, this);
|
||||||
mDatabaseManager = DatabaseManager.getInstance(getActivity());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,8 +77,8 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
|||||||
// Set the adapter
|
// Set the adapter
|
||||||
Context context = view.getContext();
|
Context context = view.getContext();
|
||||||
mList.setLayoutManager(new LinearLayoutManager(context));
|
mList.setLayoutManager(new LinearLayoutManager(context));
|
||||||
mCursorAdapter = new AlarmsCursorAdapter(this);
|
mAdapter = new AlarmsAdapter(Collections.<Alarm>emptyList(), this);
|
||||||
mList.setAdapter(mCursorAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,20 +101,20 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public android.support.v4.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<List<Alarm>> onCreateLoader(int id, Bundle args) {
|
||||||
return new AlarmsListCursorLoader(getActivity());
|
return new AlarmListLoader(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(android.support.v4.content.Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<List<Alarm>> loader, List<Alarm> data) {
|
||||||
// Called on the main thread after loading is complete
|
mAdapter.replaceData(data);
|
||||||
mCursorAdapter.swapCursor(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<List<Alarm>> loader) {
|
||||||
// The adapter's current cursor should not be used anymore
|
// Can't pass in null, because replaceData() will try to add all the elements
|
||||||
mCursorAdapter.swapCursor(null);
|
// from the given collection, so we would run into an NPE.
|
||||||
|
mAdapter.replaceData(Collections.<Alarm>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -125,6 +126,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
|||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CREATE_ALARM:
|
case REQUEST_CREATE_ALARM:
|
||||||
|
// TODO: notifyItemInserted?
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
case REQUEST_EDIT_ALARM:
|
case REQUEST_EDIT_ALARM:
|
||||||
Alarm deletedAlarm;
|
Alarm deletedAlarm;
|
||||||
@ -132,6 +134,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
|||||||
EditAlarmActivity.EXTRA_DELETED_ALARM)) != null) {
|
EditAlarmActivity.EXTRA_DELETED_ALARM)) != null) {
|
||||||
onListItemDeleted(deletedAlarm);
|
onListItemDeleted(deletedAlarm);
|
||||||
}
|
}
|
||||||
|
// TODO: notifyItemRemoved?
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -241,8 +241,10 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
|
|||||||
Log.d(TAG, "Cancelling old alarm first");
|
Log.d(TAG, "Cancelling old alarm first");
|
||||||
cancelAlarm(mOldAlarm, false);
|
cancelAlarm(mOldAlarm, false);
|
||||||
}
|
}
|
||||||
|
// TODO: Do this in the background. AsyncTask?
|
||||||
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm);
|
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Do this in the background. AsyncTask?
|
||||||
mDatabaseManager.insertAlarm(alarm);
|
mDatabaseManager.insertAlarm(alarm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 7/2/2016.
|
||||||
|
*/
|
||||||
|
public class AlarmListLoader extends DataListLoader<Alarm, AlarmCursor> {
|
||||||
|
|
||||||
|
public AlarmListLoader(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why not just have one method where we just call DatabaseManager.getAlarms()?
|
||||||
|
// I.e. why not load the cursor and extract the Alarms from it all in one?
|
||||||
|
// I figure if the loader is interrupted in the middle of loading, the underlying
|
||||||
|
// cursor won't be closed...
|
||||||
|
|
||||||
|
// TODO: If we end up doing it this way, then delete the redundant methods in DatabaseManager.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AlarmCursor loadCursor() {
|
||||||
|
return DatabaseManager.getInstance(getContext()).queryAlarms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Alarm> loadItems(AlarmCursor cursor) {
|
||||||
|
ArrayList<Alarm> alarms = new ArrayList<>();
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
alarms.add(cursor.getAlarm());
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return alarms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.CursorWrapper;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 7/2/2016.
|
||||||
|
*/
|
||||||
|
// TODO: Consider C extends MyTypeBoundedCursorWrapper<D>
|
||||||
|
public abstract class DataListLoader<D, C extends CursorWrapper> extends AsyncTaskLoader<List<D>> {
|
||||||
|
|
||||||
|
private C mCursor;
|
||||||
|
private List<D> mItems;
|
||||||
|
|
||||||
|
public DataListLoader(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract C loadCursor();
|
||||||
|
protected abstract List<D> loadItems(C cursor);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<D> loadInBackground() {
|
||||||
|
mCursor = loadCursor();
|
||||||
|
if (mCursor != null) {
|
||||||
|
// Ensure that the content window is filled
|
||||||
|
// Ensure that the data is available in memory once it is
|
||||||
|
// passed to the main thread
|
||||||
|
mCursor.getCount();
|
||||||
|
}
|
||||||
|
return loadItems(mCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<D> items) {
|
||||||
|
if (isReset()) {
|
||||||
|
// An async query came in while the loader is stopped
|
||||||
|
if (mCursor != null) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mItems = items;
|
||||||
|
if (isStarted()) {
|
||||||
|
super.deliverResult(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: might not be necessary. The analogue of this was
|
||||||
|
// to close the *old* cursor after assigning the new cursor.
|
||||||
|
// This is closing the current cursor? But then again, we don't
|
||||||
|
// care about the cursor after we've extracted the items from it..
|
||||||
|
// Close the cursor because it is no longer needed.
|
||||||
|
if (mCursor != null && !mCursor.isClosed()) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mCursor != null && mItems != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mItems);
|
||||||
|
}
|
||||||
|
if (takeContentChanged() || mCursor == null || mItems == null) {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// Attempt to cancel the current load task if possible.
|
||||||
|
cancelLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<D> data) {
|
||||||
|
if (mCursor != null && !mCursor.isClosed()) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
super.onReset();
|
||||||
|
// Ensure the loader is stopped
|
||||||
|
onStopLoading();
|
||||||
|
if (mCursor != null && !mCursor.isClosed()) {
|
||||||
|
mCursor.close();
|
||||||
|
}
|
||||||
|
mCursor = null;
|
||||||
|
mItems = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,10 +63,20 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #queryAlarms()} */
|
/** @deprecated Use {@link #queryAlarms()} */
|
||||||
|
// TODO: Possible redundant. See AlarmListLoader.
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public ArrayList<Alarm> getAlarms() {
|
public ArrayList<Alarm> getAlarms() {
|
||||||
|
return getAlarms(mHelper.queryAlarms());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Possible redundant. See AlarmListLoader.
|
||||||
|
public ArrayList<Alarm> getEnabledAlarms() {
|
||||||
|
return getAlarms(mHelper.queryEnabledAlarms());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Possible redundant. See AlarmListLoader.
|
||||||
|
private ArrayList<Alarm> getAlarms(AlarmCursor cursor) {
|
||||||
ArrayList<Alarm> alarms = new ArrayList<>();
|
ArrayList<Alarm> alarms = new ArrayList<>();
|
||||||
AlarmCursor cursor = mHelper.queryAlarms();
|
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
alarms.add(cursor.getAlarm());
|
alarms.add(cursor.getAlarm());
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import android.support.v4.content.AsyncTaskLoader;
|
|||||||
* Efficiently loads and holds a Cursor.
|
* Efficiently loads and holds a Cursor.
|
||||||
*/
|
*/
|
||||||
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
|
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
|
||||||
|
private static final String TAG = "SQLiteCursorLoader";
|
||||||
|
|
||||||
private Cursor mCursor;
|
private Cursor mCursor;
|
||||||
|
|
||||||
public SQLiteCursorLoader(Context context) {
|
public SQLiteCursorLoader(Context context) {
|
||||||
@ -18,6 +20,7 @@ public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
|
|||||||
|
|
||||||
protected abstract Cursor loadCursor();
|
protected abstract Cursor loadCursor();
|
||||||
|
|
||||||
|
/* Runs on a worker thread */
|
||||||
@Override
|
@Override
|
||||||
public Cursor loadInBackground() {
|
public Cursor loadInBackground() {
|
||||||
Cursor cursor = loadCursor();
|
Cursor cursor = loadCursor();
|
||||||
@ -30,20 +33,28 @@ public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Runs on the UI thread */
|
||||||
@Override
|
@Override
|
||||||
public void deliverResult(Cursor data) {
|
public void deliverResult(Cursor cursor) {
|
||||||
|
if (isReset()) {
|
||||||
|
// An async query came in while the loader is stopped
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
Cursor oldCursor = mCursor;
|
Cursor oldCursor = mCursor;
|
||||||
mCursor = data;
|
mCursor = cursor;
|
||||||
|
|
||||||
if (isStarted()) {
|
if (isStarted()) {
|
||||||
super.deliverResult(data);
|
super.deliverResult(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the old cursor because it is no longer needed.
|
// Close the old cursor because it is no longer needed.
|
||||||
// Because an existing cursor may be cached and redelivered, it is important
|
// 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
|
// to make sure that the old cursor and the new cursor are not the
|
||||||
// same before the old cursor is closed.
|
// same before the old cursor is closed.
|
||||||
if (oldCursor != null && oldCursor != data && !oldCursor.isClosed()) {
|
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
|
||||||
oldCursor.close();
|
oldCursor.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,6 +196,7 @@ public final class AlarmUtils {
|
|||||||
private static void save(Context c, Alarm alarm) {
|
private static void save(Context c, Alarm alarm) {
|
||||||
// AlarmsRepository.getInstance(c).saveItems();
|
// AlarmsRepository.getInstance(c).saveItems();
|
||||||
// Update the same alarm
|
// Update the same alarm
|
||||||
|
// TODO: Do this in the background. AsyncTask?
|
||||||
DatabaseManager.getInstance(c).updateAlarm(alarm.id(), alarm);
|
DatabaseManager.getInstance(c).updateAlarm(alarm.id(), alarm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user