Local broadcast receiver used as observer in loader

This commit is contained in:
Phillip Hsu 2016-07-06 00:40:52 -07:00
parent 9fb4727051
commit cf46160b1e
6 changed files with 92 additions and 45 deletions

View File

@ -5,7 +5,6 @@ import android.os.AsyncTask;
import android.support.design.widget.Snackbar;
import android.view.View;
import com.philliphsu.clock2.alarms.AlarmsAdapter;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.util.AlarmUtils;
@ -14,18 +13,16 @@ import com.philliphsu.clock2.util.AlarmUtils;
*
* TODO: Generify this class for any item.
*/
public final class AsyncRecyclerViewItemChangeHandler {
public final class AsyncItemChangeHandler {
private final Context mContext;
private final AlarmsAdapter mAdapter;
private final View mSnackbarAnchor;
/**
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
*/
public AsyncRecyclerViewItemChangeHandler(AlarmsAdapter adapter, View snackbarAnchor) {
mContext = snackbarAnchor.getContext();
mAdapter = adapter;
public AsyncItemChangeHandler(Context context, View snackbarAnchor) {
mContext = context.getApplicationContext(); // to prevent memory leaks
mSnackbarAnchor = snackbarAnchor;
}
@ -39,7 +36,7 @@ public final class AsyncRecyclerViewItemChangeHandler {
@Override
protected void onPostExecute(Long aLong) {
// TODO: Snackbar/Toast here? If so, remove the code in AlarmUtils.scheduleAlarm() that does it.
mAdapter.addItem(alarm);
// Then, consider scheduling the alarm in the background.
AlarmUtils.scheduleAlarm(mContext, alarm, true);
}
}.execute();
@ -49,8 +46,6 @@ public final class AsyncRecyclerViewItemChangeHandler {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
mAdapter.updateItem(oldAlarm, newAlarm);
// Save the item to the db
return DatabaseManager.getInstance(mContext).updateAlarm(oldAlarm.id(), newAlarm);
}
@ -65,14 +60,11 @@ public final class AsyncRecyclerViewItemChangeHandler {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
mAdapter.removeItem(alarm);
// Save the item to the db
return DatabaseManager.getInstance(mContext).deleteAlarm(alarm);
}
@Override
protected void onPostExecute(Integer integer) {
// TODO: Snackbar
if (mSnackbarAnchor != null) {
String message = mContext.getString(R.string.snackbar_item_deleted,
mContext.getString(R.string.alarm));

View File

@ -3,8 +3,8 @@ package com.philliphsu.clock2.alarms;
import android.app.Activity;
import android.content.Context;
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;
@ -17,30 +17,25 @@ import android.view.View;
import android.view.ViewGroup;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.AsyncRecyclerViewItemChangeHandler;
import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
import com.philliphsu.clock2.model.AlarmListLoader;
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.util.AlarmUtils;
import java.util.Collections;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
// TODO: Use native fragments since we're targeting API >=19?
// TODO: Use native LoaderCallbacks.
public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Alarm>>,
public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
OnListItemInteractionListener<Alarm> {
private static final int REQUEST_EDIT_ALARM = 0;
public static final int REQUEST_CREATE_ALARM = 1;
private static final String TAG = "AlarmsFragment";
private AsyncRecyclerViewItemChangeHandler mAsyncRecyclerViewItemChangeHandler;
private AlarmsAdapter mAdapter;
private AlarmsCursorAdapter mAdapter;
@Bind(R.id.list) RecyclerView mList;
@ -80,11 +75,8 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
// Set the adapter
Context context = view.getContext();
mList.setLayoutManager(new LinearLayoutManager(context));
mAdapter = new AlarmsAdapter(Collections.<Alarm>emptyList(), this);
mAdapter = new AlarmsCursorAdapter(this);
mList.setAdapter(mAdapter);
mAsyncRecyclerViewItemChangeHandler = new AsyncRecyclerViewItemChangeHandler(
mAdapter, getActivity().findViewById(R.id.main_content));
return view;
}
@ -107,20 +99,18 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
}
@Override
public Loader<List<Alarm>> onCreateLoader(int id, Bundle args) {
return new AlarmListLoader(getActivity());
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new AlarmsListCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<List<Alarm>> loader, List<Alarm> data) {
mAdapter.replaceData(data);
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<List<Alarm>> loader) {
// Can't pass in null, because replaceData() will try to add all the elements
// from the given collection, so we would run into an NPE.
mAdapter.replaceData(Collections.<Alarm>emptyList());
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
@Override
@ -132,14 +122,9 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<List<Ala
switch (requestCode) {
case REQUEST_CREATE_ALARM:
Log.d(TAG, "Async add alarm");
final Alarm createdAlarm = data.getParcelableExtra(EditAlarmActivity.EXTRA_MODIFIED_ALARM);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mAsyncRecyclerViewItemChangeHandler.asyncAddAlarm(createdAlarm);
}
}, 1000);
// TODO: Should we still do the async add here?
// We must if we want the async handler to post the toast/snackbar
// for us.
break;
case REQUEST_EDIT_ALARM:
Alarm deletedAlarm;

View File

@ -26,6 +26,7 @@ import android.widget.Toast;
import android.widget.ToggleButton;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.AsyncItemChangeHandler;
import com.philliphsu.clock2.BaseActivity;
import com.philliphsu.clock2.DaysOfWeek;
import com.philliphsu.clock2.R;
@ -68,6 +69,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
private Uri mSelectedRingtoneUri;
private Alarm mOldAlarm;
private DatabaseManager mDatabaseManager;
private AsyncItemChangeHandler mAsyncItemChangeHandler;
@Bind(R.id.save) Button mSave;
@Bind(R.id.delete) Button mDelete;
@ -98,6 +100,8 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
showDetails();
}
setTimeTextHint(); // TODO: private access
mAsyncItemChangeHandler = new AsyncItemChangeHandler(this, null);
}
@Override
@ -246,13 +250,15 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
mDatabaseManager.updateAlarm(mOldAlarm.id(), alarm);
} else {
//mDatabaseManager.insertAlarm(alarm);
intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
mAsyncItemChangeHandler.asyncAddAlarm(alarm);
//intent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
}
if (alarm.isEnabled()) {
//scheduleAlarm(alarm);
}
// TODO: Remove intent
setResult(RESULT_OK, intent);
showEditorClosed();
}

View File

@ -9,6 +9,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.util.LocalBroadcastHelper;
import static com.philliphsu.clock2.DaysOfWeek.FRIDAY;
import static com.philliphsu.clock2.DaysOfWeek.MONDAY;
@ -65,8 +66,13 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
private static final String SORT_ORDER =
COLUMN_RING_TIME_MILLIS + " ASC, " + COLUMN_ID + " ASC";
private final Context mAppContext;
public AlarmDatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION_1);
// Since DatabaseManager calls this with the application
// context, we can safely hold onto this context.
mAppContext = context;
}
@Override
@ -105,6 +111,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
long id = getWritableDatabase().insert(TABLE_ALARMS,
null, toContentValues(alarm));
alarm.setId(id);
notifyContentChanged();
return id;
}
@ -115,26 +122,32 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
newAlarm.setId(oldAlarm.id());
SQLiteDatabase db = getWritableDatabase();
return db.update(TABLE_ALARMS,
int rowsUpdated = db.update(TABLE_ALARMS,
toContentValues(newAlarm),
COLUMN_ID + " = " + newAlarm.id(),
null);
notifyContentChanged();
return rowsUpdated;
}
public int updateAlarm(long id, Alarm newAlarm) {
newAlarm.setId(id);
SQLiteDatabase db = getWritableDatabase();
return db.update(TABLE_ALARMS,
int rowsUpdated = db.update(TABLE_ALARMS,
toContentValues(newAlarm),
COLUMN_ID + " = " + id,
null);
notifyContentChanged();
return rowsUpdated;
}
public int deleteAlarm(Alarm alarm) {
SQLiteDatabase db = getWritableDatabase();
return db.delete(TABLE_ALARMS,
int rowsDeleted = db.delete(TABLE_ALARMS,
COLUMN_ID + " = " + alarm.id(),
null);
notifyContentChanged();
return rowsDeleted;
}
public AlarmCursor queryAlarm(long id) {
@ -184,6 +197,12 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
return values;
}
private void notifyContentChanged() {
Log.d(TAG, "notifyContentChanged()");
LocalBroadcastHelper.sendBroadcast(mAppContext,
SQLiteCursorLoader.ACTION_CHANGE_CONTENT);
}
// 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

View File

@ -1,8 +1,13 @@
package com.philliphsu.clock2.model;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
import com.philliphsu.clock2.util.LocalBroadcastHelper;
/**
* Created by Phillip Hsu on 6/28/2016.
@ -12,7 +17,10 @@ import android.support.v4.content.AsyncTaskLoader;
public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
private static final String TAG = "SQLiteCursorLoader";
public static final String ACTION_CHANGE_CONTENT = "com.philliphsu.clock2.model.action.CHANGE_CONTENT";
private Cursor mCursor;
private OnContentChangeReceiver mOnContentChangeReceiver;
public SQLiteCursorLoader(Context context) {
super(context);
@ -66,30 +74,54 @@ public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
if (mCursor != null) {
deliverResult(mCursor);
}
if (mOnContentChangeReceiver == null) {
mOnContentChangeReceiver = new OnContentChangeReceiver();
LocalBroadcastHelper.registerReceiver(getContext(),
mOnContentChangeReceiver, ACTION_CHANGE_CONTENT);
}
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;
if (mOnContentChangeReceiver != null) {
LocalBroadcastHelper.unregisterReceiver(getContext(),
mOnContentChangeReceiver);
mOnContentChangeReceiver = null;
}
}
private final class OnContentChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received content change event");
onContentChanged();
}
}
}

View File

@ -1,7 +1,9 @@
package com.philliphsu.clock2.util;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
/**
@ -14,5 +16,16 @@ public final class LocalBroadcastHelper {
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(action));
}
/** Registers a BroadcastReceiver that filters intents by the action specified */
// TODO: Refactor any local registrations to use this utility method.
public static void registerReceiver(Context context, BroadcastReceiver receiver, String action) {
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, new IntentFilter(action));
}
// TODO: Refactor any local unregistrations to use this utility method.
public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
}
private LocalBroadcastHelper() {}
}