Update and delete alarms now async

This commit is contained in:
Phillip Hsu 2016-07-07 00:30:45 -07:00
parent 4d2ef227b6
commit 3a86aaf3ba
3 changed files with 108 additions and 52 deletions

View File

@ -49,16 +49,28 @@ public final class AsyncItemChangeHandler {
}.execute();
}
public void asyncUpdateAlarm(final Alarm oldAlarm, final Alarm newAlarm) {
/**
* We only need one Alarm param because we called newAlarm.setId(oldAlarm.id())
* when we were in the edit activity.
* TODO: Consider changing the signature of updateAlarm() in DatabaseManager and
* AlarmDatabaseHelper to only require one Alarm param.
*/
public void asyncUpdateAlarm(final Alarm newAlarm) {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
return DatabaseManager.getInstance(mContext).updateAlarm(oldAlarm.id(), newAlarm);
return DatabaseManager.getInstance(mContext).updateAlarm(newAlarm.id(), newAlarm);
}
@Override
protected void onPostExecute(Integer integer) {
// TODO: Snackbar/Toast here? If so, remove the code in AlarmUtils.scheduleAlarm() that does it.
AlarmUtils.scheduleAlarm(mContext, newAlarm, true);
// The new alarm could have a different sort order from the old alarm.
// TODO: Sometimes this won't scrolls to the new alarm if the old alarm is
// towards the bottom and the new alarm is ordered towards the top. This
// may have something to do with us breaking the stable id guarantee?
mScrollHandler.setScrollToStableId(newAlarm.id());
}
}.execute();
}

View File

@ -6,7 +6,6 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
@ -23,8 +22,6 @@ import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.util.AlarmUtils;
import butterknife.Bind;
import butterknife.ButterKnife;
@ -39,6 +36,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
private AlarmsCursorAdapter mAdapter;
private AsyncItemChangeHandler mAsyncItemChangeHandler;
private Handler mHandler = new Handler();
private long mScrollToStableId = RecyclerView.NO_ID;
@Bind(R.id.list) RecyclerView mList;
@ -67,7 +65,6 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
// TODO Read arguments
}
// Initialize the loader to load the list of runs
getLoaderManager().initLoader(0, null, this);
}
@ -125,32 +122,36 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult()");
if (resultCode != Activity.RESULT_OK) {
if (resultCode != Activity.RESULT_OK || data == null)
return;
final Alarm alarm = data.getParcelableExtra(EditAlarmActivity.EXTRA_MODIFIED_ALARM);
if (alarm == null)
return;
}
// http://stackoverflow.com/a/27055512/5055032
// "RecyclerView does not run animations in the first layout
// pass after being attached." A workaround is to postpone
// the CRUD operation to the next frame. A delay of 300ms is
// short enough to not be noticeable, and long enough to
// give us the animation *most of the time*.
switch (requestCode) {
case REQUEST_CREATE_ALARM:
if (data != null) {
final Alarm createdAlarm = data.getParcelableExtra(EditAlarmActivity.EXTRA_MODIFIED_ALARM);
if (createdAlarm != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mAsyncItemChangeHandler.asyncAddAlarm(createdAlarm);
}
}, 300);
}
}
mHandler.postDelayed(
new AsyncAddItemRunnable(mAsyncItemChangeHandler, alarm),
300);
break;
case REQUEST_EDIT_ALARM:
Alarm deletedAlarm;
if (data != null && (deletedAlarm = data.getParcelableExtra(
EditAlarmActivity.EXTRA_MODIFIED_ALARM)) != null) {
onListItemDeleted(deletedAlarm);
if (data.getBooleanExtra(EditAlarmActivity.EXTRA_IS_DELETING, false)) {
// TODO: Should we delay this too? It seems animations run
// some of the time.
mAsyncItemChangeHandler.asyncRemoveAlarm(alarm);
} else {
// TODO: Increase the delay, because update animation is
// more elusive than insert.
mHandler.postDelayed(
new AsyncUpdateItemRunnable(mAsyncItemChangeHandler, alarm),
300);
}
// TODO: notifyItemRemoved?
getLoaderManager().restartLoader(0, null, this);
break;
default:
Log.i(TAG, "Could not handle request code " + requestCode);
@ -192,25 +193,67 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
mScrollToStableId = RecyclerView.NO_ID;
}
// TODO: This doesn't need to be defined in the interface.
// TODO: Rename to showDeletedSnackbar() or something
// TODO: This needs to prompt a reload of the list.
@Deprecated // TODO: Delete this method.
@Deprecated
@Override
public void onListItemDeleted(final Alarm item) {
Snackbar.make(getActivity().findViewById(R.id.main_content),
getString(R.string.snackbar_item_deleted, "Alarm"),
Snackbar.LENGTH_LONG) // TODO: not long enough?
.setAction(R.string.snackbar_undo_item_deleted, new View.OnClickListener() {
@Override
public void onClick(View v) {
DatabaseManager.getInstance(getActivity()).insertAlarm(item);
getLoaderManager().restartLoader(0, null, AlarmsFragment.this);
if (item.isEnabled()) {
AlarmUtils.scheduleAlarm(getActivity(), item, true);
}
}
})
.show();
// TODO: This doesn't need to be defined in the interface.
// TODO: Delete this method.
}
private static abstract class BaseAsyncItemChangeRunnable {
// TODO: Will holding onto this cause a memory leak?
private final AsyncItemChangeHandler mAsyncItemChangeHandler;
private final Alarm mAlarm;
BaseAsyncItemChangeRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) {
mAsyncItemChangeHandler = asyncItemChangeHandler;
mAlarm = alarm;
}
void asyncAddAlarm() {
mAsyncItemChangeHandler.asyncAddAlarm(mAlarm);
}
void asyncUpdateAlarm() {
mAsyncItemChangeHandler.asyncUpdateAlarm(mAlarm);
}
void asyncRemoveAlarm() {
mAsyncItemChangeHandler.asyncRemoveAlarm(mAlarm);
}
}
private static class AsyncAddItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncAddItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm);
}
@Override
public void run() {
asyncAddAlarm();
}
}
private static class AsyncUpdateItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncUpdateItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm);
}
@Override
public void run() {
asyncUpdateAlarm();
}
}
private static class AsyncRemoveItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncRemoveItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm);
}
@Override
public void run() {
asyncRemoveAlarm();
}
}
}

View File

@ -59,6 +59,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
private static final String TAG = "EditAlarmActivity";
public static final String EXTRA_ALARM_ID = "com.philliphsu.clock2.editalarm.extra.ALARM_ID";
public static final String EXTRA_MODIFIED_ALARM = "com.philliphsu.clock2.editalarm.extra.MODIFIED_ALARM";
public static final String EXTRA_IS_DELETING = "com.philliphsu.clock2.editalarm.extra.IS_DELETING";
private static final RelativeSizeSpan AMPM_SIZE_SPAN = new RelativeSizeSpan(0.5f);
private static final int REQUEST_PICK_RINGTONE = 0;
@ -211,12 +212,12 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
showRingtonePickerDialog();
}
// TODO: Privatize accessor methods
@OnClick(R.id.save)
void save() {
int hour;
int minutes;
try {
// TODO: Privatize accessor methods
hour = getHour();
minutes = getMinutes();
} catch (IllegalStateException e) {
@ -242,16 +243,15 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
Log.d(TAG, "Cancelling old alarm first");
cancelAlarm(mOldAlarm, false);
}
// TODO: Do this in the background. AsyncTask?
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm);
} else {
//mDatabaseManager.insertAlarm(alarm);
intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
alarm.setId(mOldAlarm.id());
intent.putExtra(EXTRA_IS_DELETING, false);
}
intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
if (alarm.isEnabled()) {
//scheduleAlarm(alarm);
}
// The reason we don't schedule the alarm here is AlarmUtils
// will attempt to retrieve the specified alarm
// from the database; however, the alarm hasn't yet
// been added to the database at this point.
setResult(RESULT_OK, intent);
showEditorClosed();
@ -269,6 +269,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
}
mDatabaseManager.deleteAlarm(mOldAlarm);
Intent intent = new Intent();
intent.putExtra(EXTRA_IS_DELETING, true);
intent.putExtra(EXTRA_MODIFIED_ALARM, mOldAlarm);
setResult(RESULT_OK, intent);
}