运行 离开主线程时同步调用 Cloud Firestore
Making synchronous calls to Cloud Firestore when running off the main thread
我正在基于 Android Clean Architecture Kotlin 版本 (https://github.com/android10/Android-CleanArchitecture-Kotlin) 构建应用程序。
使用这种架构,每次你想调用一个用例时,都会启动一个 Kotlin 协程,并将结果发布在主线程中。这是通过以下代码实现的:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
在他的示例架构中,Android10 先生使用 Retrofit 在 kotlin 协程内进行同步 api 调用。例如:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' 表示不相交的类型,这意味着结果要么是 Failure 要么是你想要的类型 T 的对象。
他的 service.movies() 方法是这样实现的(使用改造)
@GET(MOVIES) fun movies(): Call<List<MovieEntity>>
现在是我的问题。我正在用 Google Cloud Firestore 替换改造。我知道目前 Firebase/Firestore 是一个全异步库。我想知道是否有人知道一种更优雅的方法来对 Firebase 进行同步 API 调用。
我实现了自己的调用版本:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
和我的 API 调用在这里实现
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
当然,最后的while循环让我很困扰。有没有其他更优雅的方法,等待响应被赋值之前return来自 movieListResponse
方法?
我尝试在从 Firebase get()
方法 return 编辑的任务上调用 await(),但是 movieListResponse
方法会立即 return 。感谢您的帮助!
这不是 firebase 的工作方式。 Firebase 基于回调。
推荐架构组件的livedata
请检查以下示例。
这里是 link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0
这是过度设计的,有几个层试图做同样的事情。我建议你退后几步,取消抽象,进入直接使用协程的心情。根据this template实现一个suspend fun
。您不需要 Either
的拐杖,以最自然的方式处理异常:try-catch
围绕 suspend fun
调用。
您应该得到如下签名:
suspend fun movieList(): List<MovieEntity>
呼叫站点:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
所以我在 Google 任务中找到了我要找的东西 API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
所以我之前有问题的代码变成了:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
注意我不再需要 Thread.sleep while 循环。
此代码只能 运行 在后台 thread/kotlin 协程中。
我正在基于 Android Clean Architecture Kotlin 版本 (https://github.com/android10/Android-CleanArchitecture-Kotlin) 构建应用程序。
使用这种架构,每次你想调用一个用例时,都会启动一个 Kotlin 协程,并将结果发布在主线程中。这是通过以下代码实现的:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
在他的示例架构中,Android10 先生使用 Retrofit 在 kotlin 协程内进行同步 api 调用。例如:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' 表示不相交的类型,这意味着结果要么是 Failure 要么是你想要的类型 T 的对象。
他的 service.movies() 方法是这样实现的(使用改造)
@GET(MOVIES) fun movies(): Call<List<MovieEntity>>
现在是我的问题。我正在用 Google Cloud Firestore 替换改造。我知道目前 Firebase/Firestore 是一个全异步库。我想知道是否有人知道一种更优雅的方法来对 Firebase 进行同步 API 调用。
我实现了自己的调用版本:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
和我的 API 调用在这里实现
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
当然,最后的while循环让我很困扰。有没有其他更优雅的方法,等待响应被赋值之前return来自 movieListResponse
方法?
我尝试在从 Firebase get()
方法 return 编辑的任务上调用 await(),但是 movieListResponse
方法会立即 return 。感谢您的帮助!
这不是 firebase 的工作方式。 Firebase 基于回调。
推荐架构组件的livedata
请检查以下示例。
这里是 link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0
这是过度设计的,有几个层试图做同样的事情。我建议你退后几步,取消抽象,进入直接使用协程的心情。根据this template实现一个suspend fun
。您不需要 Either
的拐杖,以最自然的方式处理异常:try-catch
围绕 suspend fun
调用。
您应该得到如下签名:
suspend fun movieList(): List<MovieEntity>
呼叫站点:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
所以我在 Google 任务中找到了我要找的东西 API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
所以我之前有问题的代码变成了:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
注意我不再需要 Thread.sleep while 循环。 此代码只能 运行 在后台 thread/kotlin 协程中。