使用RxJava和Retrofit处理不同的流

时间:2017-05-14 03:52:50

标签: android retrofit rx-java observable

上下文

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

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

我们的Interactors正在使用Repositories,它使用Retrofit层来执行服务器端请求并返回rx.Observable<List<SomeModelClass>>之类的数据。我们的Interactors也使用返回数据库数据的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()Subscriber#onError()中的304s处理200个请求。这就是Repository内部实现的外观:

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响应处理为某些意外的&#34;意外&#34;。不能责怪他们,这不是最好的解决方案。但我尝试了很多东西,这是唯一有效的方法。我的问题是;有没有办法在不使用例外的情况下实现这一目标?一些更清洁的方式?

我尽可能地简化了这一点,但请告诉我是否有你不能得到的东西。

1 个答案:

答案 0 :(得分:1)

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

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方法的返回类型即可使用Data而不只是map而不是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包装器类,您仍然可以通过应用程序的不同层传递所需的元数据,同时仍然不会泄漏实现细节。