RoomDbとRetrofitにNetworkBoundResource
とResource
ヘルパークラスを実装しようとすると、完璧に機能します。ただし、部屋なしでのみレトロフィットを使用してRESTfulからの検索結果を実装する必要があります。 Resources
クラスは適切であり、変更する必要はありません。私がやりたいのは、このクラス内のdbソースを削除することです。
public abstract class NetworkBoundResource<ResultType, RequestType> {
private final AppExecutors appExecutors;
private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
@MainThread
public NetworkBoundResource(AppExecutors appExecutors) {
this.appExecutors = appExecutors;
result.setValue(Resource.loading(null));
LiveData<ResultType> dbSource = loadFromDb();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldFetch(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
}
});
}
@MainThread
private void setValue(Resource<ResultType> newValue) {
if (!Objects.equals(result.getValue(), newValue)) {
result.setValue(newValue);
}
}
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 -> 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 -> setValue(Resource.success(newData)))
);
});
} else {
onFetchFailed();
result.addSource(dbSource,
newData -> 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(@Nullable ResultType data);
@NonNull
@MainThread
protected abstract LiveData<ResultType> loadFromDb();
@NonNull
@MainThread
protected abstract LiveData<ApiResponse<RequestType>> createCall();
}
問題は、ロードされたデータが最初にデータベースを通過し、次にNetworkBoundResource
と同様に、データベースからUIにロードする必要があることです。したがって、私が行ったことは、永続データベースを分離し、ロード元の一時フィールドを作成することです。
たとえば、 original 検索メソッドを編集したい場合は、次のことをお勧めします。
_public LiveData<Resource<List<Repo>>> search(String query) {
return new NetworkBoundResource<List<Repo>, RepoSearchResponse>(appExecutors) {
// Temp ResultType
private List<Repo> resultsDb;
@Override
protected void saveCallResult(@NonNull RepoSearchResponse item) {
// if you don't care about order
resultsDb = item.getItems();
}
@Override
protected boolean shouldFetch(@Nullable List<Repo> data) {
// always fetch.
return true;
}
@NonNull
@Override
protected LiveData<List<Repo>> loadFromDb() {
if (resultsDb == null) {
return AbsentLiveData.create();
}else {
return new LiveData<List<Repo>>() {
@Override
protected void onActive() {
super.onActive();
setValue(resultsDb);
}
};
}
}
@NonNull
@Override
protected LiveData<ApiResponse<RepoSearchResponse>> createCall() {
return githubService.searchRepos(query);
}
@Override
protected RepoSearchResponse processResponse(ApiResponse<RepoSearchResponse> response) {
RepoSearchResponse body = response.body;
if (body != null) {
body.setNextPage(response.getNextPage());
}
return body;
}
}.asLiveData();
}
_
私はそれを実行し、それは動作します。
編集:それを処理するために別のより単純なクラスを作成しました(Daniel Wilsonによる別の回答があり、より多くの機能があり、更新されています)。
ただし、このクラスには依存関係がなく、フェッチ応答のみを行うために基本に変換されます。
_abstract class NetworkBoundResource<RequestType> {
private val result = MediatorLiveData<Resource<RequestType>>()
init {
setValue(Resource.loading(null))
fetchFromNetwork()
}
@MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
when (response) {
is ApiSuccessResponse -> {
setValue(Resource.success(processResponse(response)))
}
is ApiErrorResponse -> {
onFetchFailed()
setValue(Resource.error(response.errorMessage, null))
}
}
}
}
protected fun onFetchFailed() {
}
fun asLiveData() = result as LiveData<Resource<RequestType>>
@WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
@MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
_
したがって、それを使用する場合、実装できるメソッドは1つだけですcreateCall()
:
_fun login(email: String, password: String) = object : NetworkBoundResource<Envelope<User>>() {
override fun createCall() = api.login(email, password)
}.asLiveData()
_
久しぶりの試みです!
_abstract class NetworkOnlyResource<ResultType, RequestType>
@MainThread constructor(private val appExecutors: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>() //List<Repo>
private val request = MediatorLiveData<Resource<RequestType>>() //RepoSearchResponse
init {
result.value = Resource.loading(null)
fetchFromNetwork()
}
@MainThread
private fun setResultValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork() {
val apiResponse = createCall()
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
response?.let {
if (response.isSuccessful) {
appExecutors.diskIO().execute({
val requestType = processResponse(response)
val resultType = processResult(requestType)
appExecutors.mainThread().execute({
setResultValue(Resource.success(resultType))
}
)
})
} else {
val errorMessage = when (response.errorThrowable) {
is HttpException -> "An error has occurred: ${response.errorThrowable.code()} Please try again."
is SocketTimeoutException -> "A timeout error has occurred, please check your internet connection and try again"
is IOException -> "An IO error has occurred, most likely a network issue. Please check your internet connection and try again"
is UnauthorizedCredentialsException -> "This user name or password is not recognized"
else -> {
response.errorMessage
}
}
Timber.e(errorMessage)
errorMessage?.let {
val requestType = processResponse(response)
val resultType = processResult(requestType)
setResultValue(Resource.error(errorMessage, resultType, response.errorThrowable))
}
onFetchFailed()
}
}
}
}
protected open fun onFetchFailed() {}
fun asLiveData() = result as LiveData<Resource<ResultType>>
@WorkerThread
protected open fun processResponse(response: ApiResponse<RequestType>) = response.body
@WorkerThread
protected abstract fun processResult(item: RequestType?): ResultType?
@MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
_
processResult()
関数を使用すると、成功したRequestTypeをResultTypeに変換できます。それは私にとってはうまくいくようですが、彼らが何をしているのかを知っている誰かからのフィードバックが欲しいです:)
Fyi Yigitは、NetworkBoundResourceを更新して、エラー処理を改善しました。これは、失敗した 'else'ステートメントでも機能するはずです。
これが私がいつか書いた私のバージョンです:
import Android.Arch.lifecycle.LiveData
import Android.Arch.lifecycle.MediatorLiveData
import Android.support.annotation.MainThread
/**
* A generic class to send loading event up-stream when fetching data
* only from network.
*
* @param <RequestType>
</RequestType></ResultType> */
abstract class NetworkResource<RequestType> @MainThread constructor() {
/**
* The final result LiveData
*/
private val result = MediatorLiveData<Resource<RequestType>>()
init {
// Send loading state to UI
result.value = Resource.loading()
fetchFromNetwork()
}
/**
* Fetch the data from network and then send it upstream to UI.
*/
private fun fetchFromNetwork() {
val apiResponse = createCall()
// Make the network call
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
// Dispatch the result
response?.apply {
when {
status.isSuccessful() -> setValue(this)
else -> setValue(Resource.error(errorMessage))
}
}
}
}
@MainThread
private fun setValue(newValue: Resource<RequestType>) {
if (result.value != newValue) result.value = newValue
}
fun asLiveData(): LiveData<Resource<RequestType>> {
return result
}
@MainThread
protected abstract fun createCall(): LiveData<Resource<RequestType>>
}