web-dev-qa-db-ja.com

部屋のないNetworkBoundResourceヘルパークラス

RoomDbとRetrofitにNetworkBoundResourceResourceヘルパークラスを実装しようとすると、完璧に機能します。ただし、部屋なしでのみレトロフィットを使用して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();
}
12
Long Ranger

問題は、ロードされたデータが最初にデータベースを通過し、次に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()
_
7
amrro

久しぶりの試みです!

_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'ステートメントでも機能するはずです。

3
Daniel Wilson

これが私がいつか書いた私のバージョンです:

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>>
}
2
Akshay Chordiya