2

我正在开发一个全新的 android 应用程序,我想使用 Kotlin Flow 实现离线优先策略,就像我们使用 RxJava 一样。当我使用 Rx 时,我将以下代码用于离线优先功能。

private fun getOfflineFirst(param: Param): Observable<T> =
    Observable.concatArrayEagerDelayError(
        getOffline(param), getRemote(param)

getOffline 和 getRemote 函数将返回一个可观察对象。我使用下面的代码来使用 Flow 实现相同的结果。

private suspend fun getOfflineFirst(param: Param) = flow {
    getLocal(param)
        .onCompletion {
            getRemote(param).collect {
                emit(it)
            }
        }.collect { emit(it) }
}

getLocal 和 getRemote 将返回一个 Flow 对象。我还在我的一个游乐场项目中使用了另一种逻辑,如下所示:

    suspend fun getResult(param: PARAM, strategy: QueryStrategy): Flow<ResultResponse> = flow {
    if (strategy.isRemote()) {
        emit(getRemoteResult(param))
    } else {
        emit(getLocalResult(param))
        emit(getRemoteResult(param))
    }
}

在“else”部分,它将先发出本地结果,然后发出远程,这就是我在这种情况下首次离线处理的方式。

但我不确定我是否使用了最好的方法。

有人可以建议我一些更好的方法吗?

4

3 回答 3

2

使用这个抽象类来处理数据获取和存储。

  /**
  * A repository which provides resource from local database as well as remote 
   endpoint.
  *
  * [RESULT] represents the type for database.
  * [REQUEST] represents the type for network.
  */
  @ExperimentalCoroutinesApi
  abstract class NetworkBoundRepository<RESULT, REQUEST> {

   fun asFlow() = flow<State<RESULT>> {

    // Emit Loading State
    emit(State.loading())

    try {
        // Emit Database content first
        emit(State.success(fetchFromLocal().first()))

        // Fetch latest posts from remote
        val apiResponse = fetchFromRemote()

        // Parse body
        val remotePosts = apiResponse.body()

        // Check for response validation
        if (apiResponse.isSuccessful && remotePosts != null) {
            // Save posts into the persistence storage
            saveRemoteData(remotePosts)
        } else {
            // Something went wrong! Emit Error state.
            emit(State.error(apiResponse.message()))
        }
    } catch (e: Exception) {
        // Exception occurred! Emit error
        emit(State.error("Network error! Can't get latest data."))
        e.printStackTrace()
    }

    // Retrieve posts from persistence storage and emit
    emitAll(fetchFromLocal().map {
        State.success<RESULT>(it)
    })
}

/**
 * Saves retrieved from remote into the persistence storage.
 */
@WorkerThread
protected abstract suspend fun saveRemoteData(response: REQUEST)

/**
 * Retrieves all data from persistence storage.
 */
@MainThread
protected abstract fun fetchFromLocal(): Flow<RESULT>

/**
 * Fetches [Response] from the remote end point.
 */
@MainThread
protected abstract suspend fun fetchFromRemote(): Response<REQUEST>
}

并在您的 repo 类中传递您的 api 接口和数据库 repo

class Repository(private val api: ApiInterface, private val db: DBRepository) {

/**
 * Fetched the posts from network and stored it in database. At the end, data from 
 persistence
 * storage is fetched and emitted.
 */
fun getAllArticles(): Flow<State<List<Article>>> {
    return object : NetworkBoundRepository<List<Article>, ArticlesResponse>() {

        override suspend fun saveRemoteData(response: ArticlesResponse) =
                db.getNewsDao().insertALLItems(response.article!!)

        override fun fetchFromLocal(): Flow<List<Article>> = 
      db.getNewsDao().getItems()

        override suspend fun fetchFromRemote(): Response<ArticlesResponse> = 
        api.getArticles()

      }.asFlow().flowOn(Dispatchers.IO)
    }
 }
于 2020-07-25T09:19:17.063 回答
1

首先,这取决于您希望有多少可变性。其次,有 Dropbox 的Store解决方案,它是针对这些问题的更通用(和复杂)的解决方案。

我不太了解你的第一个函数 getOfflineFirst,它可以像你在 else 分支中的第二个例子一样完成。

于 2020-07-24T12:06:26.190 回答
1

我不会建议这种方法。首先,collect()从另一个flow到另一个不是一个好的选择flow。其次,您必须在两者之间做出决定,Rx并且Flow您不需要两者。

您始终可以使用该map()方法。我会做这样的事情:

getData() = getLocal(param)
   .map{ whatGetLocalReturns ->
     return@map getRemote(param)
    }
   .map{ whatGetRemoteReturns ->
     return@map decideResult(whatGetRemoteReturns, strategy)
   }

然后你可以这样做:

getData().collect{ result ->
  // there you go :) 
}

我跳过了一些细节,但我相信你能明白我的意思。当然,不要忘记线程和一切。

于 2020-07-24T12:55:08.040 回答