Repository created
This commit is contained in:
parent
15b850d9bd
commit
e80596060b
@ -1,6 +1,9 @@
|
|||||||
package com.philliphsu.clock2;
|
package com.philliphsu.clock2;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
|
import com.philliphsu.clock2.model.JsonSerializable;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -16,12 +19,12 @@ import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
|
|||||||
* Created by Phillip Hsu on 5/26/2016.
|
* Created by Phillip Hsu on 5/26/2016.
|
||||||
*/
|
*/
|
||||||
@AutoValue
|
@AutoValue
|
||||||
public abstract class Alarm {
|
public abstract class Alarm implements JsonSerializable {
|
||||||
private static final int MAX_MINUTES_CAN_SNOOZE = 30; // TODO: Delete this along with all snooze stuff.
|
private static final int MAX_MINUTES_CAN_SNOOZE = 30; // TODO: Delete this along with all snooze stuff.
|
||||||
|
|
||||||
// JSON property names
|
// JSON property names
|
||||||
private static final String KEY_ENABLED = "enabled";
|
private static final String KEY_ENABLED = "enabled";
|
||||||
private static final String KEY_ID = "id";
|
//private static final String KEY_ID = "id"; // Defined in JsonSerializable
|
||||||
private static final String KEY_HOUR = "hour";
|
private static final String KEY_HOUR = "hour";
|
||||||
private static final String KEY_MINUTES = "minutes";
|
private static final String KEY_MINUTES = "minutes";
|
||||||
private static final String KEY_RECURRING_DAYS = "recurring_days";
|
private static final String KEY_RECURRING_DAYS = "recurring_days";
|
||||||
@ -34,7 +37,7 @@ public abstract class Alarm {
|
|||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
// ================================
|
// ================================
|
||||||
|
|
||||||
public abstract long id(); // TODO: Counter in the repository. Set this field as the repo creates instances.
|
//public abstract long id();
|
||||||
public abstract int hour();
|
public abstract int hour();
|
||||||
public abstract int minutes();
|
public abstract int minutes();
|
||||||
@SuppressWarnings("mutable")
|
@SuppressWarnings("mutable")
|
||||||
@ -48,11 +51,16 @@ public abstract class Alarm {
|
|||||||
|
|
||||||
public static Alarm create(JSONObject jsonObject) {
|
public static Alarm create(JSONObject jsonObject) {
|
||||||
try {
|
try {
|
||||||
|
JSONArray a = (JSONArray) jsonObject.get(KEY_RECURRING_DAYS);
|
||||||
|
boolean[] recurringDays = new boolean[a.length()];
|
||||||
|
for (int i = 0; i < recurringDays.length; i++) {
|
||||||
|
recurringDays[i] = a.getBoolean(i);
|
||||||
|
}
|
||||||
Alarm alarm = new AutoValue_Alarm.Builder()
|
Alarm alarm = new AutoValue_Alarm.Builder()
|
||||||
.id(jsonObject.getLong(KEY_ID))
|
.id(jsonObject.getLong(KEY_ID))
|
||||||
.hour(jsonObject.getInt(KEY_HOUR))
|
.hour(jsonObject.getInt(KEY_HOUR))
|
||||||
.minutes(jsonObject.getInt(KEY_MINUTES))
|
.minutes(jsonObject.getInt(KEY_MINUTES))
|
||||||
.recurringDays((boolean[]) jsonObject.get(KEY_RECURRING_DAYS))
|
.recurringDays(recurringDays)
|
||||||
.label(jsonObject.getString(KEY_LABEL))
|
.label(jsonObject.getString(KEY_LABEL))
|
||||||
.ringtone(jsonObject.getString(KEY_RINGTONE))
|
.ringtone(jsonObject.getString(KEY_RINGTONE))
|
||||||
.vibrates(jsonObject.getBoolean(KEY_VIBRATES))
|
.vibrates(jsonObject.getBoolean(KEY_VIBRATES))
|
||||||
@ -69,7 +77,7 @@ public abstract class Alarm {
|
|||||||
// Fields that were not set when build() is called will throw an exception.
|
// Fields that were not set when build() is called will throw an exception.
|
||||||
// TODO: How can QualityMatters get away with not setting defaults?????
|
// TODO: How can QualityMatters get away with not setting defaults?????
|
||||||
return new AutoValue_Alarm.Builder()
|
return new AutoValue_Alarm.Builder()
|
||||||
.id(-1)
|
//.id(-1) // Set when build() is called
|
||||||
.hour(0)
|
.hour(0)
|
||||||
.minutes(0)
|
.minutes(0)
|
||||||
.recurringDays(new boolean[DaysOfWeek.NUM_DAYS])
|
.recurringDays(new boolean[DaysOfWeek.NUM_DAYS])
|
||||||
@ -78,9 +86,6 @@ public abstract class Alarm {
|
|||||||
.vibrates(false);
|
.vibrates(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
// TODO: Snoozing functionality not necessary. Delete methods.
|
|
||||||
|
|
||||||
public void snooze(int minutes) {
|
public void snooze(int minutes) {
|
||||||
if (minutes <= 0 || minutes > MAX_MINUTES_CAN_SNOOZE)
|
if (minutes <= 0 || minutes > MAX_MINUTES_CAN_SNOOZE)
|
||||||
throw new IllegalArgumentException("Cannot snooze for "+minutes+" minutes");
|
throw new IllegalArgumentException("Cannot snooze for "+minutes+" minutes");
|
||||||
@ -99,8 +104,6 @@ public abstract class Alarm {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
@ -169,6 +172,8 @@ public abstract class Alarm {
|
|||||||
return ringsIn() <= hours * 3600000;
|
return ringsIn() <= hours * 3600000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
public JSONObject toJsonObject() {
|
public JSONObject toJsonObject() {
|
||||||
try {
|
try {
|
||||||
return new JSONObject()
|
return new JSONObject()
|
||||||
@ -187,6 +192,7 @@ public abstract class Alarm {
|
|||||||
|
|
||||||
@AutoValue.Builder
|
@AutoValue.Builder
|
||||||
public abstract static class Builder {
|
public abstract static class Builder {
|
||||||
|
private static long idCount = 0;
|
||||||
// Builder is mutable, so these are inherently setter methods.
|
// Builder is mutable, so these are inherently setter methods.
|
||||||
// By omitting the set- prefix, we reduce the number of changes required to define the Builder
|
// By omitting the set- prefix, we reduce the number of changes required to define the Builder
|
||||||
// class after copying and pasting the accessor fields here.
|
// class after copying and pasting the accessor fields here.
|
||||||
@ -212,6 +218,7 @@ public abstract class Alarm {
|
|||||||
/*not public*/abstract Alarm autoBuild();
|
/*not public*/abstract Alarm autoBuild();
|
||||||
|
|
||||||
public Alarm build() {
|
public Alarm build() {
|
||||||
|
this.id(idCount++);
|
||||||
Alarm alarm = autoBuild();
|
Alarm alarm = autoBuild();
|
||||||
checkTime(alarm.hour(), alarm.minutes());
|
checkTime(alarm.hour(), alarm.minutes());
|
||||||
return alarm;
|
return alarm;
|
||||||
|
|||||||
@ -13,7 +13,8 @@ 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.alarms.dummy.DummyContent;
|
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||||
|
import com.philliphsu.clock2.model.BaseRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
@ -21,7 +22,7 @@ import com.philliphsu.clock2.alarms.dummy.DummyContent;
|
|||||||
* 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 {
|
public class AlarmsFragment extends Fragment implements BaseRepository.DataObserver<Alarm> {
|
||||||
|
|
||||||
// TODO: Customize parameter argument names
|
// TODO: Customize parameter argument names
|
||||||
private static final String ARG_COLUMN_COUNT = "column-count";
|
private static final String ARG_COLUMN_COUNT = "column-count";
|
||||||
@ -29,6 +30,9 @@ public class AlarmsFragment extends Fragment {
|
|||||||
private int mColumnCount = 1;
|
private int mColumnCount = 1;
|
||||||
private OnAlarmInteractionListener mListener;
|
private OnAlarmInteractionListener mListener;
|
||||||
|
|
||||||
|
private AlarmsAdapter mAdapter;
|
||||||
|
private AlarmsRepository mRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||||
* fragment (e.g. upon screen orientation changes).
|
* fragment (e.g. upon screen orientation changes).
|
||||||
@ -68,17 +72,19 @@ public class AlarmsFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
|
recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
|
||||||
}
|
}
|
||||||
recyclerView.setAdapter(new AlarmsAdapter(DummyContent.ITEMS, mListener));
|
mAdapter = new AlarmsAdapter(mRepo.getItems(), mListener);
|
||||||
|
recyclerView.setAdapter(mAdapter);
|
||||||
}
|
}
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
if (context instanceof OnAlarmInteractionListener) {
|
if (context instanceof OnAlarmInteractionListener) {
|
||||||
mListener = (OnAlarmInteractionListener) context;
|
mListener = (OnAlarmInteractionListener) context;
|
||||||
|
mRepo = AlarmsRepository.getInstance(context);
|
||||||
|
mRepo.registerDataObserver(this);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException(context.toString()
|
throw new RuntimeException(context.toString()
|
||||||
+ " must implement OnAlarmInteractionListener");
|
+ " must implement OnAlarmInteractionListener");
|
||||||
@ -91,6 +97,21 @@ public class AlarmsFragment extends Fragment {
|
|||||||
mListener = null;
|
mListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemAdded(Alarm item) {
|
||||||
|
mAdapter.addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemDeleted(Alarm item) {
|
||||||
|
mAdapter.removeItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemUpdated(Alarm oldItem, Alarm newItem) {
|
||||||
|
mAdapter.updateItem(oldItem, newItem);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|||||||
@ -7,11 +7,14 @@ import android.widget.CheckBox;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.Alarm;
|
||||||
import com.philliphsu.clock2.BaseActivity;
|
import com.philliphsu.clock2.BaseActivity;
|
||||||
import com.philliphsu.clock2.DaysOfWeek;
|
import com.philliphsu.clock2.DaysOfWeek;
|
||||||
import com.philliphsu.clock2.R;
|
import com.philliphsu.clock2.R;
|
||||||
|
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
|
||||||
public class EditAlarmActivity extends BaseActivity {
|
public class EditAlarmActivity extends BaseActivity {
|
||||||
|
|
||||||
@ -47,6 +50,11 @@ public class EditAlarmActivity extends BaseActivity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.save)
|
||||||
|
void save() {
|
||||||
|
AlarmsRepository.getInstance(this).addItem(Alarm.builder().build());
|
||||||
|
}
|
||||||
|
|
||||||
private void setWeekDaysText() {
|
private void setWeekDaysText() {
|
||||||
for (int i = 0; i < mDays.length; i++) {
|
for (int i = 0; i < mDays.length; i++) {
|
||||||
int weekDay = DaysOfWeek.getInstance(this).weekDay(i);
|
int weekDay = DaysOfWeek.getInstance(this).weekDay(i);
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public class AlarmIoHelper extends JsonIoHelper<Alarm> {
|
||||||
|
private static final String FILENAME = "alarms.json";
|
||||||
|
|
||||||
|
public AlarmIoHelper(@NonNull Context context) {
|
||||||
|
super(context, FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Alarm newItem(@NonNull JSONObject jsonObject) {
|
||||||
|
return Alarm.create(jsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.Alarm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public class AlarmsRepository extends BaseRepository<Alarm> {
|
||||||
|
private static final String TAG = "AlarmsRepository";
|
||||||
|
// Singleton, so this is the sole instance for the lifetime
|
||||||
|
// of the application; thus, instance fields do not need to
|
||||||
|
// be declared static because they are already associated with
|
||||||
|
// this single instance. Since no other instance can exist,
|
||||||
|
// any member fields are effectively class fields.
|
||||||
|
// **
|
||||||
|
// Can't be final, otherwise you'd need to instantiate inline
|
||||||
|
// or in static initializer, but ctor requires Context so you
|
||||||
|
// can't do that either.
|
||||||
|
private static AlarmsRepository sRepo;
|
||||||
|
|
||||||
|
private AlarmsRepository(@NonNull Context context) {
|
||||||
|
super(context, new AlarmIoHelper(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlarmsRepository getInstance(@NonNull Context context) {
|
||||||
|
if (null == sRepo) {
|
||||||
|
Log.d(TAG, "Loading AlarmsRepository for the first time");
|
||||||
|
sRepo = new AlarmsRepository(context);
|
||||||
|
}
|
||||||
|
return sRepo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public abstract class BaseRepository<T extends JsonSerializable> implements Repository<T> {
|
||||||
|
private static final String TAG = "BaseRepository";
|
||||||
|
// Cannot do this! Multiple classes will extend from this,
|
||||||
|
// so this "singleton" would be a class property for all of them.
|
||||||
|
//private static BaseRepositoryV2<T> sInstance;
|
||||||
|
|
||||||
|
// Never used since subclasses provide the ioHelper, but I think
|
||||||
|
// the intention is that we hold onto the global context so this
|
||||||
|
// never gets GCed for the lifetime of the app.
|
||||||
|
@NonNull private final Context mContext;
|
||||||
|
@NonNull private final List<T> mItems;
|
||||||
|
@NonNull private final JsonIoHelper<T> mIoHelper;
|
||||||
|
|
||||||
|
// TODO: Test that the callbacks work.
|
||||||
|
private DataObserver<T> mDataObserver;
|
||||||
|
|
||||||
|
// We could use T but since it's already defined, we should avoid
|
||||||
|
// the needless confusion and use a different type param. You won't
|
||||||
|
// be able to refer to the type that T resolves to anyway.
|
||||||
|
public interface DataObserver<T2> {
|
||||||
|
void onItemAdded(T2 item);
|
||||||
|
void onItemDeleted(T2 item);
|
||||||
|
void onItemUpdated(T2 oldItem, T2 newItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package-private*/ BaseRepository(@NonNull Context context,
|
||||||
|
@NonNull JsonIoHelper<T> ioHelper) {
|
||||||
|
Log.d(TAG, "BaseRepositoryV2 initialized");
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mIoHelper = ioHelper; // MUST precede loading items
|
||||||
|
mItems = loadItems(); // TOneverDO: move this elsewhere
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public List<T> getItems() {
|
||||||
|
return Collections.unmodifiableList(mItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public T getItem(long id) {
|
||||||
|
for (T item : getItems())
|
||||||
|
if (item.id() == id)
|
||||||
|
return item;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void addItem(@NonNull T item) {
|
||||||
|
Log.d(TAG, "New item added");
|
||||||
|
mItems.add(item);
|
||||||
|
mDataObserver.onItemAdded(item); // TODO: Set StopwatchView as DataObserver
|
||||||
|
saveItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void deleteItem(@NonNull T item) {
|
||||||
|
if (!mItems.remove(item)) {
|
||||||
|
Log.e(TAG, "Cannot remove an item that is not in the list");
|
||||||
|
} else {
|
||||||
|
mDataObserver.onItemDeleted(item); // TODO: Set StopwatchView as DataObserver
|
||||||
|
saveItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void updateItem(@NonNull T item1, @NonNull T item2) {
|
||||||
|
// TODO: Won't work unless objects are immutable, so item1
|
||||||
|
// can't change and thus its index will never change
|
||||||
|
// **
|
||||||
|
// Actually, since the items come from this list,
|
||||||
|
// modifications to items will directly "propagate".
|
||||||
|
// In the process, the index of that modified item
|
||||||
|
// has not changed. If that's the case, there really
|
||||||
|
// isn't any point for an update method, especially
|
||||||
|
// since item2 would be unnecessary and won't even need
|
||||||
|
// to be used.
|
||||||
|
mItems.set(mItems.indexOf(item1), item2);
|
||||||
|
mDataObserver.onItemUpdated(item1, item2); // TODO: Set StopwatchView as DataObserver
|
||||||
|
saveItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean saveItems() {
|
||||||
|
try {
|
||||||
|
mIoHelper.saveItems(mItems);
|
||||||
|
Log.d(TAG, "Saved items to file");
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error writing items to file: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void clear() {
|
||||||
|
mItems.clear();
|
||||||
|
saveItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void registerDataObserver(@NonNull DataObserver<T> observer) {
|
||||||
|
mDataObserver = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do we need to call this?
|
||||||
|
public final void unregisterDataObserver() {
|
||||||
|
mDataObserver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<T> loadItems() {
|
||||||
|
try {
|
||||||
|
return mIoHelper.loadItems();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error loading items from file: " + e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public abstract class JsonIoHelper<T extends JsonSerializable> {
|
||||||
|
@NonNull private final Context mContext;
|
||||||
|
@NonNull private final String mFilename;
|
||||||
|
|
||||||
|
public JsonIoHelper(@NonNull Context context,
|
||||||
|
@NonNull String filename) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mFilename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T newItem(@NonNull JSONObject jsonObject);
|
||||||
|
|
||||||
|
public final List<T> loadItems() throws IOException {
|
||||||
|
ArrayList<T> items = new ArrayList<>();
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
// Opens the file in a FileInputStream for byte-reading
|
||||||
|
InputStream in = mContext.openFileInput(mFilename);
|
||||||
|
// Use an InputStreamReader to convert bytes to characters. A BufferedReader wraps the
|
||||||
|
// existing Reader and provides a buffer (a cache) for storing the characters.
|
||||||
|
// From https://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html:
|
||||||
|
// "In general, each read request made of a Reader causes a corresponding read request
|
||||||
|
// to be made of the underlying character or byte stream. It is therefore advisable to
|
||||||
|
// wrap a BufferedReader around any Reader whose read() operations may be costly, such as
|
||||||
|
// FileReaders and InputStreamReaders."
|
||||||
|
reader = new BufferedReader(new InputStreamReader(in));
|
||||||
|
StringBuilder jsonString = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
// Line breaks are omitted and irrelevant
|
||||||
|
jsonString.append(line);
|
||||||
|
}
|
||||||
|
// JSONTokener parses a String in JSON "notation" into a "JSON-compatible" object.
|
||||||
|
// JSON objects are instances of JSONObject and JSONArray. You actually have to call
|
||||||
|
// nextValue() on the returned Tokener to get the corresponding JSON object.
|
||||||
|
JSONArray array = (JSONArray) new JSONTokener(jsonString.toString()).nextValue();
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
items.add(newItem(array.getJSONObject(i)));
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (reader != null)
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void saveItems(@NonNull List<T> items) throws IOException {
|
||||||
|
// Convert items to JSONObjects and store in a JSONArray
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
try {
|
||||||
|
for (T item : items) {
|
||||||
|
array.put(item.toJsonObject());
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStreamWriter writer = null;
|
||||||
|
try {
|
||||||
|
// Create a character stream from the byte stream
|
||||||
|
writer = new OutputStreamWriter(mContext.openFileOutput(mFilename, Context.MODE_PRIVATE));
|
||||||
|
// Write JSONArray to file
|
||||||
|
writer.write(array.toString());
|
||||||
|
} finally {
|
||||||
|
if (writer != null) {
|
||||||
|
writer.close(); // also calls close on the byte stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public interface JsonSerializable {
|
||||||
|
String KEY_ID = "id";
|
||||||
|
|
||||||
|
@NonNull JSONObject toJsonObject() throws JSONException;
|
||||||
|
long id();
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.philliphsu.clock2.model;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Phillip Hsu on 5/31/2016.
|
||||||
|
*/
|
||||||
|
public interface Repository<T> {
|
||||||
|
@NonNull List<T> getItems();
|
||||||
|
@Nullable T getItem(long id);
|
||||||
|
void addItem(@NonNull T item);
|
||||||
|
void deleteItem(@NonNull T item);
|
||||||
|
void updateItem(@NonNull T item1, @NonNull T item2);
|
||||||
|
boolean saveItems();
|
||||||
|
void clear();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user