Repository created

This commit is contained in:
Phillip Hsu
2016-05-31 19:39:00 -07:00
parent 15b850d9bd
commit e80596060b
9 changed files with 372 additions and 14 deletions
@@ -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();
}