Square 生まれのライブラリを使います.
SQLBrite
square/sqlbrite: A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations.
SQLiteOpenHelper をラップするように使い, ContentProvider(ContentResolver) のように更新を通知する役割を担当します.
SQLBrite: A Reactive Database Foundation
RxJava と連携させて使いましょう.
SqlBrite sqlBrite = SqlBrite.create();
BriteDatabase db = sqlBrite.wrapDatabaseHelper(openHelper, Schedulers.io());
// 対象テーブル名を含むSQLクエリーを登録
Observable<Query> users = db.createQuery("users", "SELECT * FROM users");
final AtomicInteger queries = new AtomicInteger();
// データ更新を監視して受け取る
users.subscribe(new Action1<Query>() {
@Override public void call(Query query) {
queries.getAndIncrement();
}
});
System.out.println("Queries: " + queries.get()); // Prints 1
db.insert("users", createUser("jw", "Jake Wharton"));
db.insert("users", createUser("mattp", "Matt Precious"));
db.insert("users", createUser("strong", "Alec Strong"));
System.out.println("Queries: " + queries.get()); // Prints 4
このしくみを利用すると,
CursorLoader + ContentProvider
の部分を
RxJava + SQLBrite
で置き換えてリアクティブに記述できます.
MVP実装の例
googlesamples/android-architecture at dev-todo-mvp-rxjava
Observable を DataSource で作成して,
TasksLocalDataSource.java:77
@Override
public Observable<List<Task>> getTasks() {
String[] projection = {
TaskEntry.COLUMN_NAME_ENTRY_ID,
TaskEntry.COLUMN_NAME_TITLE,
TaskEntry.COLUMN_NAME_DESCRIPTION,
TaskEntry.COLUMN_NAME_COMPLETED
};
String sql = String.format("SELECT %s FROM %s", TextUtils.join(",", projection), TaskEntry.TABLE_NAME);
return mDatabaseHelper.createQuery(TaskEntry.TABLE_NAME, sql)
.mapToList(mTaskMapperFunction);
}
Repository 経由させて,
TaskRepository:96
@Override
public Observable<List<Task>> getTasks() {
// ...
if (mCacheIsDirty) {
return remoteTasks;
} else {
// Query the local storage if available. If not, query the network.
Observable<List<Task>> localTasks = mTasksLocalDataSource.getTasks();
return Observable.concat(localTasks, remoteTasks).first();
}
}
Presenter 上で subscribe しておけば,
TaskPresenter:104
Subscription subscription = mTasksRepository
.getTasks()
.flatMap(new Func1<List<Task>, Observable<Task>>() {
@Override
public Observable<Task> call(List<Task> tasks) {
return Observable.from(tasks);
}
})
.filter(new Func1<Task, Boolean>() {
// ...
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Task>>() {
@Override
public void onCompleted() {
mTasksView.setLoadingIndicator(false);
}
@Override
public void onError(Throwable e) {
mTasksView.showLoadingTasksError();
}
@Override
public void onNext(List<Task> tasks) {
processTasks(tasks);
}
});
データベースで対象テーブルに更新されるとすぐに自動的に再読み込みが実行されて画面が更新されます.
MVP に CursorLoader を利用すると, Presenter で受け取るコールバックが煩雑になりやすいのでこれを使うと便利です.
DiffUtil で簡単に RecyclerView を更新する