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(); }.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>() { new AsyncTask<Void, Void, Integer>() {
@Override @Override
protected Integer doInBackground(Void... params) { protected Integer doInBackground(Void... params) {
return DatabaseManager.getInstance(mContext).updateAlarm(oldAlarm.id(), newAlarm); return DatabaseManager.getInstance(mContext).updateAlarm(newAlarm.id(), newAlarm);
} }
@Override @Override
protected void onPostExecute(Integer integer) { protected void onPostExecute(Integer integer) {
// TODO: Snackbar/Toast here? If so, remove the code in AlarmUtils.scheduleAlarm() that does it. // 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(); }.execute();
} }

View File

@ -6,7 +6,6 @@ import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
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.v4.content.Loader;
@ -23,8 +22,6 @@ 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.AlarmsListCursorLoader;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.util.AlarmUtils;
import butterknife.Bind; import butterknife.Bind;
import butterknife.ButterKnife; import butterknife.ButterKnife;
@ -39,6 +36,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
private AlarmsCursorAdapter mAdapter; private AlarmsCursorAdapter mAdapter;
private AsyncItemChangeHandler mAsyncItemChangeHandler; private AsyncItemChangeHandler mAsyncItemChangeHandler;
private Handler mHandler = new Handler();
private long mScrollToStableId = RecyclerView.NO_ID; private long mScrollToStableId = RecyclerView.NO_ID;
@Bind(R.id.list) RecyclerView mList; @Bind(R.id.list) RecyclerView mList;
@ -67,7 +65,6 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
// TODO Read arguments // TODO Read arguments
} }
// Initialize the loader to load the list of runs
getLoaderManager().initLoader(0, null, this); getLoaderManager().initLoader(0, null, this);
} }
@ -125,32 +122,36 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult()"); 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; 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) { switch (requestCode) {
case REQUEST_CREATE_ALARM: case REQUEST_CREATE_ALARM:
if (data != null) { mHandler.postDelayed(
final Alarm createdAlarm = data.getParcelableExtra(EditAlarmActivity.EXTRA_MODIFIED_ALARM); new AsyncAddItemRunnable(mAsyncItemChangeHandler, alarm),
if (createdAlarm != null) { 300);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mAsyncItemChangeHandler.asyncAddAlarm(createdAlarm);
}
}, 300);
}
}
break; break;
case REQUEST_EDIT_ALARM: case REQUEST_EDIT_ALARM:
Alarm deletedAlarm; if (data.getBooleanExtra(EditAlarmActivity.EXTRA_IS_DELETING, false)) {
if (data != null && (deletedAlarm = data.getParcelableExtra( // TODO: Should we delay this too? It seems animations run
EditAlarmActivity.EXTRA_MODIFIED_ALARM)) != null) { // some of the time.
onListItemDeleted(deletedAlarm); 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; break;
default: default:
Log.i(TAG, "Could not handle request code " + requestCode); 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; mScrollToStableId = RecyclerView.NO_ID;
} }
// TODO: This doesn't need to be defined in the interface.
// TODO: Rename to showDeletedSnackbar() or something @Deprecated
// TODO: This needs to prompt a reload of the list.
@Deprecated // TODO: Delete this method.
@Override @Override
public void onListItemDeleted(final Alarm item) { public void onListItemDeleted(final Alarm item) {
Snackbar.make(getActivity().findViewById(R.id.main_content), // TODO: This doesn't need to be defined in the interface.
getString(R.string.snackbar_item_deleted, "Alarm"), // TODO: Delete this method.
Snackbar.LENGTH_LONG) // TODO: not long enough? }
.setAction(R.string.snackbar_undo_item_deleted, new View.OnClickListener() {
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 @Override
public void onClick(View v) { public void run() {
DatabaseManager.getInstance(getActivity()).insertAlarm(item); asyncAddAlarm();
getLoaderManager().restartLoader(0, null, AlarmsFragment.this);
if (item.isEnabled()) {
AlarmUtils.scheduleAlarm(getActivity(), item, true);
} }
} }
})
.show(); 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"; 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_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_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 RelativeSizeSpan AMPM_SIZE_SPAN = new RelativeSizeSpan(0.5f);
private static final int REQUEST_PICK_RINGTONE = 0; private static final int REQUEST_PICK_RINGTONE = 0;
@ -211,12 +212,12 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
showRingtonePickerDialog(); showRingtonePickerDialog();
} }
// TODO: Privatize accessor methods
@OnClick(R.id.save) @OnClick(R.id.save)
void save() { void save() {
int hour; int hour;
int minutes; int minutes;
try { try {
// TODO: Privatize accessor methods
hour = getHour(); hour = getHour();
minutes = getMinutes(); minutes = getMinutes();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
@ -242,16 +243,15 @@ 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? alarm.setId(mOldAlarm.id());
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm); intent.putExtra(EXTRA_IS_DELETING, false);
} else { }
//mDatabaseManager.insertAlarm(alarm);
intent.putExtra(EXTRA_MODIFIED_ALARM, alarm); intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
}
if (alarm.isEnabled()) { // The reason we don't schedule the alarm here is AlarmUtils
//scheduleAlarm(alarm); // 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); setResult(RESULT_OK, intent);
showEditorClosed(); showEditorClosed();
@ -269,6 +269,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
} }
mDatabaseManager.deleteAlarm(mOldAlarm); mDatabaseManager.deleteAlarm(mOldAlarm);
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(EXTRA_IS_DELETING, true);
intent.putExtra(EXTRA_MODIFIED_ALARM, mOldAlarm); intent.putExtra(EXTRA_MODIFIED_ALARM, mOldAlarm);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
} }