【问题标题】:Repository pattern with SqlBrite/SqlDelight(Offline database) and Retrofit(Http request)带有 SqlBrite/SqlDelight(离线数据库)和 Retrofit(Http 请求)的存储库模式
【发布时间】:2017-10-17 18:42:00
【问题描述】:

我正在使用 SqlBrite/SqlDelight 在 RxJava 中实现存储库模式,用于离线数据存储和 Http 请求的改造

这是一个示例:

protected Observable<List<Item>> getItemsFromDb() {
     return database.createQuery(tableName(), selectAllStatement())
             .mapToList(cursor -> selectAllMapper().map(cursor));
 }


public Observable<List<Item>>getItems(){
     Observable<List<Item>> server = getRequest()
                 .doOnNext(items -> {
                     BriteDatabase.Transaction transaction = database.newTransaction();
                     for (Item item : items){
                         database.insert(tableName(), contentValues(item));
                     }
                     transaction.markSuccessful();
                     transaction.end();
                 })
                 .flatMap(items -> getItemsFromDbById())
                 .delaySubscription(200, TimeUnit.MILLISECONDS);
         Observable<List<Item>> db = getItemsFromDbById(id)
                 .filter(items -> items != null && items.size() > 0);
     return Observable.amb(db, server).doOnSubscribe(() -> server.subscribe(items -> {}, throwable -> {}));
 }

当前实现使用Observable.amb 获取最新的2 个流,并在db 有数据或服务器的情况下返回db 流。为了防止在没有互联网的情况下提前失败,server 上面有一个delaySubscription200ms

我尝试使用 Observable.concat,但 SqlBrite 流从不调用 onComplete,因此永远不会触发 server observable。

我也尝试了Observable.combineLatest,但没有成功,因为它一直在等待server observable 在发出任何东西之前返回数据,而Observable.switchOnNext 也没有工作。

我正在寻找的是一个存储库:

  • 保持对 SqlBrite (DB) 的订阅处于打开状态,以防数据库更新
  • 始终从服务器获取数据并将其写入数据库
  • 如果数据库中没有任何内容并且网络请求仍在进行中,则不应发出空结果。这是因为在第一次加载的情况下,用户应该会看到一个进度条。

【问题讨论】:

  • 我不明白你想要什么。
  • @DeanXu 我已经更新了问题。
  • 让我更正一下,您想一次执行 2 个操作,并希望在成功的情况下合并结果,如果失败,您想用观察者管理该流程!?

标签: android retrofit rx-java sqlbrite sqldelight


【解决方案1】:

这就是解决上述问题的方法,即从 2 个来源(本地和远程)获取数据并仅在需要时向 UI 发送更新。

数据类包装了你的数据,也存储了数据的来源

class Data<T> {

    static final int STATE_LOCAL = 0;
    static final int STATE_SERVER = 1;

    private T data;
    private int state;

    Data(T data, int state) {
        this.data = data;
        this.state = state;
    }

    public int getState() { return state; }

    public T getData() { return data; }
}

...

public Observable<Model> getData(long id) {

    // Used to cache data and compare it with server data, so we can avoid unnecessary UI updates
    Subject<Data<Model>> publishSubject = BehaviorSubject.create();
    publishSubject.onNext(new Data<>(null, Data.STATE_LOCAL));

    Observable<Data<Model>> server = getRequest()
            .map(items -> new Data<>(items, Data.STATE_SERVER))
            // Here we are combining data from server and our `BehaviorSubject`
            // If any one has ideas how to do this without the subject, I'll be glad to hear it.
            .flatMap(items -> Observable.zip(publishSubject.take(1), Observable.just(items), Pair::new))
            .flatMap(oldNewPair -> {
                // Here we are comparing old and new data to see if there was any new data returned from server
                Data<Model> prevData = oldNewPair.first;
                Data<Model> newData = oldNewPair.second;
                //Could be any condition to compare the old and new data
                if (prevData.data != null && prevData.data.updated_at() == newData.data.updated_at()) 
                    return Observable.just(prevData);
                else
                    return database.insert(tableName(), contentValues(newData));

                return getFromDb(id)
                        .map(item -> new Data<>(item, Data.STATE_LOCAL))
                        .onErrorResumeNext(server)
                        .doOnNext(item -> {
                            publishSubject.onNext(item);
                            if (item.getState() == Data.STATE_LOCAL)
                                server.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe();
                        })
                        .map(item -> item.data);
}

这个解决方案没有使用amb,而是使用了解决以下问题的BehaviorSubject:

  1. 不使用delaySubscription(早期用于防止在没有互联网的情况下提前失败。)

  2. 之前,每次对服务器进行两次调用,在这种情况下解决了。

【讨论】:

    【解决方案2】:

    您的代码与您想要做的直接矛盾。这一行:

         Observable<List<Item>> db = getItemsFromDbById(id)
                 .filter(items -> items != null && items.size() > 0);
    

    自相矛盾,因为您返回单个数据库查询的项目并将其命名为 db - 就好像数据库(或其引用)本身一样。从这一点来看,您提供的代码显然无济于事。

    存储库模式有许多可用的 java 模板。例如: https://www.bignerdranch.com/blog/the-rxjava-repository-pattern/

    如果这没有足够的帮助,请尝试提供至少与您所描述的内容相去甚远的代码。

    【讨论】:

    • Is a contradiction to itself because you return single database query's items and name it db - as if the database(or its reference) itself. 你能解释一下吗? database 是 SQLBriteDatabase 实例,如果数据库中有新内容,它会自动触发更新。如果有新的东西,可观察的db 将发出一个新的数据列表。
    猜你喜欢
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    相关资源
    最近更新 更多