谷歌的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;
鉴于这个逻辑是正确的解释,我试图重构这个类以使用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版本相同的逻辑?
答案 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();
}
}
所以这是我在多次迭代后决定使用的内容。这是目前正在生产中,并适用于我的应用程序。以下是一些带走的说明:
创建BaseResponse
界面
public interface BaseResponse {
void saveResponseToDb(AppDatabase appDatabase);
}
并在所有api响应对象类中实现它。这样做意味着您不必在每个ApiResource中实现save_to_database逻辑,如果需要,您可以将其默认为响应的实现。
为了简单起见,我选择在onErrorResumeNext块中处理Retrofit错误响应,但我建议你创建一个可以容纳所有这些逻辑的Transformer类。在这种情况下,我为ApiResources添加了一个名为Status
的额外INVALID
枚举值,用于400级响应。
您可能想要使用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)。因此,我们必须管理此流的生命周期,这首先会破坏使用它的目的,或者每次设备旋转时都会重复调用。
以下是创建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();
}
}