如何将Android任务转换为Kotlin延迟对象?

24
Firebase匿名登录返回一个任务(基本上是Google承诺实现):
val task:Task<AuthResult> = FirebaseAuth.getInstance().signInAnonymously()

如何创建一个signInAnonymous的包装器,使其具有以下特点:

  • 它是一个suspend函数,等待task完成

    • suspend fun signInAnonymous(): Unit
  • 它返回一个Deferred对象,以异步方式传递结果

    • fun signInAnonymous() : Deferred
4个回答

27

kotlinx.coroutines.tasks 现在包含以下实用函数:

public suspend fun <T> Task<T>.await(): T { ... }

文档中得知:
等待任务完成,而不会阻塞线程。
这个挂起函数是可取消的。
如果当前协程的Job在此挂起函数等待期间被取消或完成,则此函数停止等待完成阶段并立即恢复执行,抛出CancellationException
public fun <T> Task<T>.asDeferred(): Deferred<T> { ... }

文档中得知:
将此任务转换为Deferred实例。
如果任务被取消,则所得的deferred也将被取消。
因此,您只需执行以下操作:
suspend fun signInAnonymouslyAwait(): AuthResult {
    return FirebaseAuth.getInstance().signInAnonymously().await()
}

或者:

fun signInAnonymouslyDeferred(): Deferred<AuthResult> {
    return FirebaseAuth.getInstance().signInAnonymously().asDeferred()
}

4
对于那些寻找Maven仓库的人,它是kotlinx-coroutines-play-services - RickSanchez725
博客解释在这里 https://betterprogramming.pub/how-to-use-kotlin-coroutines-with-firebase-6f8577a3e00f - Rahul Kahale

15

基于这个 GitHub 库,以下是一种将 Task 转换为协程挂起函数的方法,以“通常”的方式来适应基于回调的异步调用到协程的转换:

suspend fun <T> Task<T>.await(): T = suspendCoroutine { continuation ->
    addOnCompleteListener { task ->
        if (task.isSuccessful) {
            continuation.resume(task.result)
        } else {
            continuation.resumeWithException(task.exception ?: RuntimeException("Unknown task exception"))
        }
    }
}

当然,你也可以将其包装在一个Deferred中,这里CompletableDeferred非常有用:

fun <T> Task<T>.asDeferred(): Deferred<T> {
    val deferred = CompletableDeferred<T>()

    deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
            // optional, handle coroutine cancellation however you'd like here
        }
    }

    this.addOnSuccessListener { result -> deferred.complete(result) }
    this.addOnFailureListener { exception -> deferred.completeExceptionally(exception) }

    return deferred
}

如何从此函数返回值?@zsmb13这两个函数都需要在async内部调用。例如:val tokenFirebase: String = FirebaseInstanceId.getInstance().instanceId.asDeferred().await().tokenval tokenFirebase: String = FirebaseInstanceId.getInstance().instanceId.await().token// await()会显示一个警告,而应该只从协程或另一个挂起函数中调用suspend函数'await' - mochadwi
是的,Deferred上的await和我答案中提供的await都需要在协程内部调用,因为它们都是挂起函数。但是我不确定您具体要问些什么。如果您想知道如何启动一个协程,请参见协程指南 - zsmb13
在我的情况下,我想从Firebase获取令牌,但如何从异步协程返回值?我尝试过以下代码: val token = with(FirebaseInstanceId.getInstance().instanceId) { /*GlobalScope.async { asDeferred().await() }*/ // for what? we await() this? if (asDeferred().isCompleted && result != null && isSuccessful) result?.token ?: "" else "" } token }@zsmb13但是仍然返回空白或空字符串,或者我使用的方法不正确?谢谢 - mochadwi

6
将以下内容添加到gradle中。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3'

接下来您可以这样使用它:

suspend fun login(email: String, pass: String) {
    FirebaseAuth.getInstance().signInWithEmailAndPassword(email, pass).await()
}

4
要将其转化为协程可用的函数,我会使用来自Tasks API的Tasks.await()函数:
suspend fun FirebaseAuth.signInAnonymouslyAwait(): AuthResult {
    return Tasks.await(this.signInAnonymously())
}

关于 Deferred,我建议你采纳 zsmb13 的回答。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接