Repository created
This commit is contained in:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user