【问题标题】:Handling different flows with RxJava and Retrofit使用 RxJava 和 Retrofit 处理不同的流程
【发布时间】:2017-10-13 02:24:03
【问题描述】:

上下文:

我有一个使用带有 RxJava 的 Retrofit2 来处理服务器端响应的 Android 应用程序。服务器端知道如何处理 etag,因此服务器可以将请求响应返回为 200(成功)或 304(未修改)。问题是我们希望能够在Subscriber#onNext() 中以不同方式处理这些响应。例如,我们有一个使用 SQLlite 实现的本地数据库,因此当服务器端请求返回 200 时,我们希望将该响应存储在本地数据库中,但如果请求响应返回 304 我们不想这样做,因为它是我们已经存储在数据库中的相同数据,再次保存是浪费时间。

我们使用Fernando Cejas Clean Architecture 作为我们的应用架构。

我们的交互器正在使用存储库,它们使用改造层来执行服务器端请求并返回像rx.Observable<List<SomeModelClass>> 这样的数据。我们的交互器也使用返回数据库数据的 DAO,例如 rx.Observable<List<SomeModelClass>>。因此,通过这样做,我们可以做一些非常酷的事情,例如:

public void GetDogsInteractor {
    ...
    public void execute (Callback callback) {
        mDogsRepository.getAllDogs()
            .onErrorNext(ObservableUtils.ifExceptionIs(SomeServerSideException.class, mDogsDao.getDatabaseDogs()))
            .subscribe(
                new Subscriber<List<Dog>>() {
                    ...
                    public void onError() {
                        callback.showError("Sorry no doggos on server-side nor database");
                    }

                    public void onNext(List<Dog> dogs) {
                        callback.showDogsOnUI(dogs);
                    }
                    ...
                }
            );
    }
    ...
}

正如您所见,我们的存储库和 DAO 具有相同的接口,这样在出现问题时可以很容易地相互插入。

解决方法:

假设我们希望仅当请求响应为 200 时才将所有 Dog 对象存储在数据库中,但如果请求响应为 304 时我们不存储。我能想到的唯一解决方案是过滤服务器 -如果响应是 304,则侧面响应并抛出异常,这样我可以处理 Subscriber#onNext() 中的 200 个请求和 Subscriber#onError() 中的 304s。这是存储库内部实现的外观:

public class DogRepository {
    ...
    public rx.Observable<List<Dog>> getAllDogs() {
        mRetrofitService.getAllDogsFromServerSide() //Returns rx.Observable<Response<List<Dog>>>
            .flatMap(new Func1<Response<List<Dog>>, Observable<List<Dog>>>() {
                @Override
                public Observable<List<Dog>> call(Response<List<Dog>> serverSideResponse) {
                    //Check the response code, if its 200 just pass the response through, if its 304 throw an exception
                    if (serverSideResponse.getCode() == 304) {
                        return Observable.error(new NotModifiedException(serverSideResponse.getBody()));
                    } else {
                        return Observable.just(serverSideResponse.getBody());
                    }
                }
            })
    }
    ...
}

然后我创建一个覆盖Subscriber#onError() 方法的自定义订阅者:

public class MySubscriber<T> extends rx.Subscriber<T> {
    ...
    public void onError(Throwable e) {
        if (e instance of NotModifiedException) {
            onNotModified(((NotModifiedException) e).getBody()); //onNotModified() is a hook method
        }
    }

    public void onNotModified(T model) {
        //Optional to implement
    }
    ...
}

那么最后我是这样用的:

public void GetDogsInteractor {
    ...
    public void execute (Callback callback) {
        mDogsRepository.getAllDogs()
            .onErrorNext(ObservableUtils.ifExceptionIs(SomeServerSideException.class, mDogsDao.getDatabaseDogs()))
            .subscribe(
                new MySubscriber<List<Dog>>() {
                    ...
                    public void onError() {
                        callback.showError("Sorry no doggos on server-side nor database");
                    }

                    public void onNotModified(List<Dog> dogs) {
                        callback.showDogsOnUI(dogs);
                    }

                    public void onNext(List<Dog> dogs) {
                        mDogDao.saveAllDogs(dogs)
                        callback.showDogsOnUI(dogs);
                    }
                    ...
                }
            );
    }
    ...
}

这样我什至可以使用 onErrorNext() 将其他流与 NotModifiedException 挂钩。

问题:

我的一些同事不太乐意处理这种抛出异常并将 304 响应作为“意外”处理。不能怪他们,这不是最好的解决方案。但我尝试了很多东西,这是唯一有效的方法。我的问题是;有没有办法在不使用异常的情况下实现这一点?更清洁的方式?

我尽可能地简化了这一点,但如果有什么你不明白的,请告诉我。

【问题讨论】:

    标签: android retrofit rx-java observable


    【解决方案1】:

    我使用Data&lt;T&gt; 包装类在应用程序的不同层之间传递内容。对于你的情况,我会有类似的东西:

    public class Data<T> {
    
        public static final int LOADING = 0;
        public static final int SUCCESS = 1;
        public static final int NO_CHANGE = 2;
        public static final int ERROR = 3;
    
        public final int status;
        public final T result;
        public final DataError error;
    
        public Data(int status, T result, DataError error) {
            this.status = status;
            this.result = result;
            this.error = error;
        }
    }
    

    您甚至可以添加静态语法糖构造函数:

    public static <R> Data<R> success(R result) {
        return new Data<>(SUCCESS, result, null);
    }
    
    public static <R> Data<R> noChange(R result) {
        return new Data<>(NO_CHANGE, result , null);
    }
    

    然后只需将Repository 方法的返回类型更改为使用Datamap 而不是flatMap

    public Observable<Data<List<Dog>>> getAllDogs() {
        mRetrofitService.getAllDogsFromServerSide()
            .map(new Func1<Response<List<Dog>>, Data<List<Dog>>>() {
                @Override
                public Data<List<Dog>> call(Response<List<Dog>> serverSideResponse) {
    
                    if (serverSideResponse.getCode() == 304) {
                        return Data.noChange(serverSideResponse.getBody());
                    } else {
                        return Data.success(serverSideResponse.getBody());
                    }
                }
            })
    }
    

    现在在您的 Interactor onNext 中,您可以简单地检查一下状态:

    public void onNext(Data<List<Dog>> data) {
    
        if (result.status == SUCCESS)
            mDogDao.saveAllDogs(data.result)
            callback.showDogsOnUI(data.result);
            return;
        }
    
        if (result.status == NO_CHANGE) {
            callback.showDogsOnUI(data.result);
            return;
        }
    
        if (result.status == ERROR) {
            //...
        }
    }
    

    使用Data 包装类允许您仍然通过应用程序的不同层传递所需的元数据,同时仍然不会泄露实现细节。

    【讨论】:

    • 好吧是个好主意,但是我的 DAO 应该开始返回 Data> 对吧?这有点问题,因为我的应用程序中有几个 DAO,我无法重构所有这些 DAO。我想为 DAO 和存储库保留相同的接口。他们都返回 rx.Observable>
    • 这会有所帮助,但并非完全强制。我实际上会说您的 Repository 应该是您的 Http/Db 类前面的门面,而不是 Http 的实现......但这是另一回事。只需在 DAO 前面添加一个适配器类,它会转换为 Data&lt;T&gt;,直到您有时间进行更彻底的重构。
    • 在为此苦苦挣扎了很长时间后,我最终实施了您的解决方案,主要是因为我有多个数据源,我必须跟踪所有这些数据源。我有带有 Retrofit 的网络层,它可以返回状态为 200 或 304 的 Data,我有一个 Database Data 并且我有一个内存缓存。所以感谢您的宝贵时间!
    猜你喜欢
    • 2016-10-29
    • 2016-08-29
    • 1970-01-01
    • 2021-05-11
    • 2017-01-01
    • 1970-01-01
    • 2017-11-24
    • 2017-09-26
    • 2023-03-15
    相关资源
    最近更新 更多