The Repositories
Eventually, our Controller
will need to update backing stores, so that our to-do items and filter modes are persistent from run to run of our app. This sample app uses the repository pattern, with two repositories: ToDoRepository
for to-do items and FilterModeRepository
for filter modes.
The objective of each repository is simple:
- Modify the backing store as needed, where the caller is required to establish the appropriate thread to do this on
- Offer an observable API to load the data
Each repository is a singleton, so all logic routes through central points for each type of data.
ToDoRepository
The ToDoRepository
wraps around a ToDoDatabase
and ToDoEntity
objects, where those have the appropriate Room annotations to set up a SQLite database.
ToDoEntity
has fields that map to the same pieces of data that ToDoModel
holds. In principle, we could have dispensed with the separation and passed around ToDoEntity
objects where we are currently passing around ToDoModel
objects. However, it is possible that some future edition of this sample might have multiple backing stores (e.g., local database and a server), in which case keeping some separation between the in-memory model and the persistent representations is worthwhile. In this case, converting between the two is relatively simple, and ToDoEntity
has a constructor and toModel()
methods that do just that:
package com.commonsware.android.todo.impl;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.PrimaryKey;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import android.support.annotation.NonNull;
import java.util.Calendar;
import java.util.List;
import io.reactivex.Single;
@Entity(tableName="todos", indices=@Index(value="id"))
public class ToDoEntity {
@PrimaryKey
@NonNull final String id;
@NonNull final String description;
final String notes;
final boolean isCompleted;
@NonNull final Calendar createdOn;
public static ToDoEntity fromModel(ToDoModel model) {
return(new ToDoEntity(model.id(), model.description(), model.isCompleted(),
model.notes(), model.createdOn()));
}
ToDoEntity(@NonNull String id, @NonNull String description, boolean isCompleted,
String notes, @NonNull Calendar createdOn) {
this.id=id;
this.description=description;
this.isCompleted=isCompleted;
this.notes=notes;
this.createdOn=createdOn;
}
public ToDoModel toModel() {
return(ToDoModel.builder()
.id(id)
.description(description)
.isCompleted(isCompleted)
.notes(notes)
.createdOn(createdOn)
.build());
}
@Dao
public interface Store {
@Query("SELECT * FROM todos ORDER BY description ASC")
Single<List<ToDoEntity>> all();
@Insert
void insert(ToDoEntity... entities);
@Update
void update(ToDoEntity... entities);
@Delete
void delete(ToDoEntity... entities);
@Delete
void delete(List<ToDoEntity> entities);
@Query("DELETE FROM todos")
void deleteAll();
}
}
ToDoRepository
then maps between the models that it uses as its API and the entities that it uses with the ToDoDatabase
:
package com.commonsware.android.todo.impl;
import android.content.Context;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Single;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
public class ToDoRepository {
private static volatile ToDoRepository INSTANCE=null;
private final ToDoDatabase db;
public synchronized static ToDoRepository get(Context ctxt) {
if (INSTANCE==null) {
INSTANCE=new ToDoRepository(ctxt.getApplicationContext());
}
return(INSTANCE);
}
private ToDoRepository(Context ctxt) {
db=ToDoDatabase.get(ctxt);
}
public Single<List<ToDoModel>> all() {
return(db.todoStore().all().map(entities -> {
ArrayList<ToDoModel> result=new ArrayList<>(entities.size());
for (ToDoEntity entity : entities) {
result.add(entity.toModel());
}
return(result);
}));
}
public void add(ToDoModel model) {
db.todoStore().insert(ToDoEntity.fromModel(model));
}
public void replace(ToDoModel model) {
db.todoStore().update(ToDoEntity.fromModel(model));
}
public void delete(List<ToDoModel> models) {
List<ToDoEntity> entities=new ArrayList<>();
for (ToDoModel model : models) {
entities.add(ToDoEntity.fromModel(model));
}
db.todoStore().delete(entities);
}
}
Its all()
method is a Single
, used for the initial data load, which gets all the entities, then uses a map()
operator to convert those entities into models.
FilterModeRepository
The filter mode is saved in SharedPreferences
. The FilterMode
itself is an enum
that knows how to map between stable int
values and the corresponding enum
values:
package com.commonsware.android.todo.impl;
public enum FilterMode {
ALL(0),
COMPLETED(1),
OUTSTANDING(2);
private final int value;
static FilterMode forValue(int value) {
FilterMode result=ALL;
if (value==1) {
result=COMPLETED;
}
else if (value==2) {
result=OUTSTANDING;
}
return(result);
}
FilterMode(int value) {
this.value=value;
}
int getValue() {
return(value);
}
}
FilterModeRepository
hides the storage details, accepting in FilterMode
objects and offering a load()
method to obtain the current FilterMode
via a Single
:
package com.commonsware.android.todo.impl;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import io.reactivex.Single;
class FilterModeRepository {
private static final String PREF_MODE="filterMode";
private static volatile FilterModeRepository INSTANCE=null;
private SharedPreferences prefs=null;
synchronized static FilterModeRepository get() {
if (INSTANCE==null) {
INSTANCE=new FilterModeRepository();
}
return(INSTANCE);
}
Single<FilterMode> load(Context ctxt) {
final Context app=ctxt.getApplicationContext();
return(Single.create(e -> {
synchronized(this) {
if (prefs==null) {
prefs=app.getSharedPreferences(getClass().getCanonicalName(),
Context.MODE_PRIVATE);
}
}
e.onSuccess(FilterMode.forValue(prefs.getInt(PREF_MODE, 0)));
}));
}
@SuppressLint("ApplySharedPref")
void save(FilterMode mode) {
prefs.edit().putInt(PREF_MODE, mode.getValue()).commit();
}
}
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.