如何在Kotlin中正确地混合协程和回调风格的实现?

6qfn3psc  于 2023-04-21  发布在  Kotlin
关注(0)|答案(1)|浏览(192)

我尝试在我的应用程序中的MVVM架构中使用Firebase身份验证。我使用Kotlin和协程,在我的暂停函数中实现Firebase回调后,我遇到了一些与Firebase回调和暂停函数的异步执行相关的概念问题。
我试图通过遵循MVVM架构在Kotlin中实现Google Signin with 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)
    }

但令人惊讶的是,它工作正常;它等待回调addOnCompleteListener完成,然后返回Envelope.Success(user!!)。
2.你知道为什么上面的代码可以正常工作吗?
非常感谢你的帮助。

lp0sw83n

lp0sw83n1#

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

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 来获取结果,而不是 addOnCompleteListener。通过使用suspendCoroutine,函数 getGoogleSignInResult 应该被挂起,直到在其中一个回调中调用continuation.resume(res)。然后它恢复并返回 res

相关问题