重构谷歌的NetworkBoundResource类以使用RxJava而不是LiveData

时间:2018-04-15 18:41:57

标签: java android rx-java android-architecture-components android-livedata

谷歌的Android架构组件教程here有一个部分解释了如何抽象通过网络获取数据的逻辑。在其中,他们使用LiveData创建一个名为NetworkBoundResource的抽象类,以创建一个被动流作为所有被动网络请求的基础。

public abstract class NetworkBoundResource<ResultType, RequestType> {
private final AppExecutors appExecutors;

private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();

@MainThread
NetworkBoundResource(AppExecutors appExecutors) {
    this.appExecutors = appExecutors;
    result.setValue(Resource.loading(null));
    LiveData<ResultType> dbSource = loadFromDb();
    result.addSource(dbSource, data -> {
        result.removeSource(dbSource);
        if (shouldFetch()) {
            fetchFromNetwork(dbSource);
        } else {
            result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
        }
    });
}

private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
    LiveData<ApiResponse<RequestType>> apiResponse = createCall();
    // we re-attach dbSource as a new source, it will dispatch its latest value quickly
    result.addSource(dbSource, newData -> result.setValue(Resource.loading(newData)));
    result.addSource(apiResponse, response -> {
        result.removeSource(apiResponse);
        result.removeSource(dbSource);
        //noinspection ConstantConditions
        if (response.isSuccessful()) {
            appExecutors.diskIO().execute(() -> {
                saveCallResult(processResponse(response));
                appExecutors.mainThread().execute(() ->
                        // we specially request a new live data,
                        // otherwise we will get immediately last cached value,
                        // which may not be updated with latest results received from network.
                        result.addSource(loadFromDb(),
                                newData -> result.setValue(Resource.success(newData)))
                );
            });
        } else {
            onFetchFailed();
            result.addSource(dbSource,
                    newData -> result.setValue(Resource.error(response.errorMessage, newData)));
        }
    });
}

protected void onFetchFailed() {
}

public LiveData<Resource<ResultType>> asLiveData() {
    return result;
}

@WorkerThread
protected RequestType processResponse(ApiResponse<RequestType> response) {
    return response.body;
}

@WorkerThread
protected abstract void saveCallResult(@NonNull RequestType item);

@MainThread
protected abstract boolean shouldFetch();

@NonNull
@MainThread
protected abstract LiveData<ResultType> loadFromDb();

@NonNull
@MainThread
protected abstract LiveData<ApiResponse<RequestType>> createCall();
}

根据我的理解,这门课程的逻辑是:

a)创建一个名为&#34;结果&#34;的MediatorLiveData。作为主要返回对象并将其初始值设置为Resource.loading(null)

b)从Android Room db获取数据作为dbSource LiveData并将其添加到&#34;结果&#34;作为源LiveData

c)在dbSource LiveData的第一次发射中,从&#34;结果&#34;中删除dbSource LiveData。并致电&#34; shouldFetchFromNetwork()&#34;

  1. 如果为TRUE,请调用&#34; fetchDataFromNetwork(dbSource)&#34;通过&#34; createCall()&#34;创建网络呼叫返回封装为ApiResponse对象的响应的LiveData
  2. 将dbSource LiveData添加回&#34;结果&#34;并将设置的值设置为Resource.loading(data)
  3. 将apiResponce LiveData添加到&#34;结果&#34;并在第一次发射时删除dbSource和apiResponce LiveDatas
  4. 如果apiResponse成功,请调用&#34; saveCallResult(processResponse(response))&#34;并将dbSource LiveData添加回&#34;结果&#34;并将发射值设置为Resource.success(newData)
  5. 如果apiResponse失败,请拨打&#34; onFetchFailed()&#34;并将dbSource LiveData添加回&#34;结果&#34;并将发出的值设置为Resource.error(response.errorMessage,newData))
  6. IF FALSE,只需将dbSource LiveData添加到&#34;结果&#34;并将发射值设置为Resource.success(newData)
  7. 鉴于这个逻辑是正确的解释,我试图重构这个类以使用RxJava Observables而不是LiveData。这是我成功重构的尝试(我删除了初始的Resource.loading(null),因为我认为这是多余的。)

    public abstract class NetworkBoundResource<ResultType, RequestType> {
    
    private Observable<Resource<ResultType>> result;
    
    @MainThread
    NetworkBoundResource() {
        Observable<Resource<ResultType>> source;
        if (shouldFetch()) {
            source = createCall()
                    .subscribeOn(Schedulers.io())
                    .doOnNext(apiResponse -> saveCallResult(processResponse(apiResponse)))
                    .flatMap(apiResponse -> loadFromDb().toObservable().map(Resource::success))
                    .doOnError(t -> onFetchFailed())
                    .onErrorResumeNext(t -> {
                        return loadFromDb()
                                .toObservable()
                                .map(data -> Resource.error(t.getMessage(), data))
    
                    })
                    .observeOn(AndroidSchedulers.mainThread());
        } else {
            source = loadFromDb()
                    .toObservable()
                    .map(Resource::success);
        }
    
        result = Observable.concat(
                loadFromDb()
                        .toObservable()
                        .map(Resource::loading)
                        .take(1),
                source
        );
    }
    
    public Observable<Resource<ResultType>> asObservable() {return result;}
    
    protected void onFetchFailed() {}
    
    @WorkerThread
    protected RequestType processResponse(ApiResponse<RequestType> response) {return response.body;}
    
    @WorkerThread
    protected abstract void saveCallResult(@NonNull RequestType item);
    
    @MainThread
    protected abstract boolean shouldFetch();
    
    @NonNull
    @MainThread
    protected abstract Flowable<ResultType> loadFromDb();
    
    @NonNull
    @MainThread
    protected abstract Observable<ApiResponse<RequestType>> createCall();
    }
    

    由于我是RxJava的新手,我的问题是我是否正确地重构为RxJava并保持与此类的LiveData版本相同的逻辑?

1 个答案:

答案 0 :(得分:5)

public abstract class ApiRepositorySource<RawResponse extends BaseResponse, ResultType> {

    // result is a Flowable because Room Database only returns Flowables
    // Retrofit response will also be folded into the stream as a Flowable
    private Flowable<ApiResource<ResultType>> result; 
    private AppDatabase appDatabase;

    @MainThread
    ApiRepositorySource(AppDatabase appDatabase) {
        this.appDatabase = appDatabase;
        Flowable<ApiResource<ResultType>> source;
        if (shouldFetch()) {
            source = createCall()
                .doOnNext(this::saveCallResult)
                .flatMap(apiResponse -> loadFromDb().toObservable().map(ApiResource::success))
                .doOnError(this::onFetchFailed)
                .onErrorResumeNext(t -> {
                    return loadFromDb()
                            .toObservable()
                            .map(data -> {
                                ApiResource apiResource;

                                if (t instanceof HttpException && ((HttpException) t).code() >= 400 && ((HttpException) t).code() < 500) {
                                    apiResource = ApiResource.invalid(t.getMessage(), data);
                                } else {
                                    apiResource = ApiResource.error(t.getMessage(), data);
                                }

                                return apiResource;
                            });
                })
                .toFlowable(BackpressureStrategy.LATEST);
        } else {
            source = loadFromDb()
                    .subscribeOn(Schedulers.io())
                    .map(ApiResource::success);
        }

        result = Flowable.concat(initLoadDb()
                            .map(ApiResource::loading)
                            .take(1),
                            source)
                .subscribeOn(Schedulers.io());
    }

    public Observable<ApiResource<ResultType>> asObservable() {
        return result.toObservable();
    }

    @SuppressWarnings("WeakerAccess")
    protected void onFetchFailed(Throwable t) {
        Timber.e(t);
    }

    @WorkerThread
    protected void saveCallResult(@NonNull RawResult resultType) {
        resultType.saveResponseToDb(appDatabase);
    }

    @MainThread
    protected abstract boolean shouldFetch();

    @NonNull
    @MainThread
    protected abstract Flowable<ResultType> loadFromDb();

    @NonNull
    @MainThread
    protected abstract Observable<RawResult> createCall();

    @NonNull
    @MainThread
    protected Flowable<ResultType> initLoadDb() {
        return loadFromDb();
    }
}

所以这是我在多次迭代后决定使用的内容。这是目前正在生产中,并适用于我的应用程序。以下是一些带走的说明:

  1. 创建BaseResponse界面

        public interface BaseResponse {
             void saveResponseToDb(AppDatabase appDatabase);
        }
    

    并在所有api响应对象类中实现它。这样做意味着您不必在每个ApiResource中实现save_to_database逻辑,如果需要,您可以将其默认为响应的实现。

  2. 为了简单起见,我选择在onErrorResumeNext块中处理Retrofit错误响应,但我建议你创建一个可以容纳所有这些逻辑的Transformer类。在这种情况下,我为ApiResources添加了一个名为Status的额外INVALID枚举值,用于400级响应。

  3. 您可能想要使用LiveData的Reactive Streams体系结构组件库

    implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version"并在此类中添加一个名为

    的方法
        public LiveData<ApiResource<ResultType>> asLiveData {
             return LiveDataReactiveStreams.fromPublisher(result);
        }
    

    理论上,这将完美地工作,因为我们的ViewModel不必将Observable排放转换为LiveData排放或实现View中Observables的生命周期逻辑。不幸的是,这个流在每次配置更改时都会重建,因为它会在所调用的任何onDestroy中处理LiveData(无论isFinishing是true还是false)。因此,我们必须管理此流的生命周期,这首先会破坏使用它的目的,或者每次设备旋转时都会重复调用。

  4. 以下是创建ApiNetworkResource实例的UserRepository示例

    @Singleton
    public class UserRepository {
    
        private final RetrofitApi retrofitApi;
        private final AppDatabase appDatabase;
    
        @Inject
        UserRepository(RetrofitApi retrofitApi, AppDatabase appDatabase) {
            this.retrofitApi = retrofitApi;
            this.appDatabase = appDatabase;
        }
    
        public Observable<ApiResource<User>> getUser(long userId) {
            return new ApiRepositorySource<UserResponse, User>(appDatabase) {
    
                @Override
                protected boolean shouldFetch() {
                    return true;
                }
    
                @NonNull
                @Override
                protected Flowable<User> loadFromDb() {
                    return appDatabase.userDao().getUserFlowable(userId);
                }
    
                @NonNull
                @Override
                protected Observable<UserResponse> createCall() {
                    return retrofitApi.getUserById(userId);
                }
            }.asObservable();
        }
    
    }