Kotlin Flow "First Offline" 方法
Kotlin Flow "First Offline" approach
我正在开发一个全新的 android 应用程序,我想使用 Kotlin Flow 实现离线优先策略,就像我们使用 RxJava 一样。当我使用 Rx 时,我使用以下代码实现离线优先功能。
private fun getOfflineFirst(param: Param): Observable<T> =
Observable.concatArrayEagerDelayError(
getOffline(param), getRemote(param)
getOffline 和 getRemote 函数将 return 一个可观察对象。
我使用下面的代码使用 Flow 获得相同的结果。
private suspend fun getOfflineFirst(param: Param) = flow {
getLocal(param)
.onCompletion {
getRemote(param).collect {
emit(it)
}
}.collect { emit(it) }
}
getLocal 和 getRemote 将 return 一个 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”部分,它将发出本地结果,然后是远程结果,这就是我在这种情况下首次离线处理的方式。
但我不确定我是否使用了可用的最佳方法。
有人可以建议我一些更好的方法吗?
首先,这取决于您希望有多少可变性。其次,Dropbox 的 Store 解决方案是针对这些问题的更通用(也更复杂)的解决方案。
我不太了解您的第一个函数 getOfflineFirst,它可以像您在 else 分支中的第二个示例中那样完成。
我不建议采用这种方法。首先,从另一个 flow
到另一个 flow
collect()
并不是一个好的选择。其次,您必须在 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 :)
}
我跳过了一些细节,但我相信你能明白我的意思。当然不要忘记线程和所有内容。
Use this abstract class to handle data fetching and storing.
/**
* 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>
}
and in your repo class pass your api interface and database 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)
}
}
我正在开发一个全新的 android 应用程序,我想使用 Kotlin Flow 实现离线优先策略,就像我们使用 RxJava 一样。当我使用 Rx 时,我使用以下代码实现离线优先功能。
private fun getOfflineFirst(param: Param): Observable<T> =
Observable.concatArrayEagerDelayError(
getOffline(param), getRemote(param)
getOffline 和 getRemote 函数将 return 一个可观察对象。 我使用下面的代码使用 Flow 获得相同的结果。
private suspend fun getOfflineFirst(param: Param) = flow {
getLocal(param)
.onCompletion {
getRemote(param).collect {
emit(it)
}
}.collect { emit(it) }
}
getLocal 和 getRemote 将 return 一个 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”部分,它将发出本地结果,然后是远程结果,这就是我在这种情况下首次离线处理的方式。
但我不确定我是否使用了可用的最佳方法。
有人可以建议我一些更好的方法吗?
首先,这取决于您希望有多少可变性。其次,Dropbox 的 Store 解决方案是针对这些问题的更通用(也更复杂)的解决方案。
我不太了解您的第一个函数 getOfflineFirst,它可以像您在 else 分支中的第二个示例中那样完成。
我不建议采用这种方法。首先,从另一个 flow
到另一个 flow
collect()
并不是一个好的选择。其次,您必须在 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 :)
}
我跳过了一些细节,但我相信你能明白我的意思。当然不要忘记线程和所有内容。
Use this abstract class to handle data fetching and storing.
/**
* 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>
}
and in your repo class pass your api interface and database 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)
}
}