如何在Kotlin中混合协程与回调风格的实现?



我正在尝试在我的应用程序中的MVVM架构中使用Firebase身份验证。我使用Kotlin和协程,并在我的挂起函数中实现Firebase回调后,我面临一些与Firebase回调和挂起函数的异步执行相关的概念问题。

我正试图通过遵循MVVM架构在Kotlin中实现与Firebase的谷歌登录。因此,我设计了一个AuthViewModel,它通过调用以下suspend函数将接收到的令牌(从Google One-tap)发送到AuthRepository:

.
.
.
suspend fun getGoogleSignInResult(token: String): Envelope<AuthUserInfo> {
return withContext(defaultDispatcher) {
when(val response = authRemoteDataSource.getGoogleSignInResult(token)) {
is Envelope.Success -> {
Envelope.Success(AuthUserInfo(response.data!!, null))
}
else -> {
Envelope.Error(response.tag!!, response.message)
}
}
}
}

这个函数,调度到defaultDispatcher并调用我的远程数据源(authRemoteDataSource)中的authRemoteDataSource. getgooglesigninresult (token),并被挂起。然后authRemoteDataSource获得控制权并按如下方式向ioDispatcher调度:

.
.
.
suspend fun getGoogleSignInResult(token: String): Envelope<FirebaseUser> {
return withContext(ioDispatcher) {
// Got an ID token from Google. Use it to authenticate
// with Firebase.
var user: FirebaseUser? = null
val job = launch {
val firebaseCredential = GoogleAuthProvider.getCredential(token, null)
auth.signInWithCredential(firebaseCredential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d("TEST", "signInWithCredential:success")
user = auth.currentUser
} else {
// If sign in fails, display a message to the user.

Log.w("TEST", "signInWithCredential:failure", task.exception)
}
}
}
job.join() // callbacks are not synchronous so we must wait for the job
user?.let { Envelope.Success(user!!) }
?: Envelope.Error(Envelope.Tag.NON_CACHED_EXCEPTION)
}
}

但是这个函数失败了,因为它在分配给addOnCompleteListener的回调完成之前返回;user为NULL,它将返回Envelope.Error(Envelope.Tag.NON_CACHED_EXCEPTION)。看起来对signInWithCredential的调用是异步的,作业没有等待它。问题是:

  1. 我可以强制Firebase同步执行吗?

顺便说一下,我将上面的代码更改为以下代码,我预计它也不会工作:

.
.
.
suspend fun getGoogleSignInResult(token: String): Envelope<FirebaseUser> {
var user: FirebaseUser? = null
val job =  withContext(ioDispatcher) {
// Got an ID token from Google. Use it to authenticate
// with Firebase.
val firebaseCredential = GoogleAuthProvider.getCredential(token, null)
auth.signInWithCredential(firebaseCredential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d("TEST", "signInWithCredential:success")
user = auth.currentUser
} else {
// If sign in fails, display a message to the user.
Log.w("TEST", "signInWithCredential:failure", task.exception)
}
}
}
// Return
job.await() // addOnCompleteListener is not synchronous so we must wait for the job
return user?.let { Envelope.Success(user!!) }
?: Envelope.Error(Envelope.Tag.NON_CACHED_EXCEPTION)
}

但令人惊讶的是,它工作正常;它等待回调addoncompletellistener完成,然后返回Envelope.Success(user!!)。

2。你知道为什么上面的代码可以正常工作吗?

非常感谢你的帮助。

我使用了suspendCoroutine将回调函数与挂起函数结合起来。现在,我将代码修改如下:

suspend fun getGoogleSignInResult(token: String): Envelope<FirebaseUser> {
return withContext(ioDispatcher) {
return@withContext suspendCoroutine { continuation ->
val firebaseCredential = GoogleAuthProvider.getCredential(token, null)
auth.signInWithCredential(firebaseCredential)
.addOnSuccessListener {
continuation.resume(Envelope.Success(it.user!!))
}
.addOnFailureListener { e ->
val res: Envelope<FirebaseUser> =
Envelope.Error(Envelope.Tag.NON_CACHED_EXCEPTION)
continuation.resume(res)
}
}
}
}

这个可以正常工作。我使用addOnSuccessListeneraddOnFailureListener来获得结果,而不是addoncompletellistener。通过使用suspendCoroutine,函数getGoogleSignInResult应该被挂起,直到continue .resume(res)在一个回调函数中被调用。然后返回res

最新更新