Kotlin协程与回调式实现结合问题:Firebase认证MVVM实践疑问
问题背景
我在基于MVVM架构的Kotlin应用中使用Firebase Authentication结合协程时,遇到了Firebase回调与挂起函数异步执行的概念问题。正在实现Firebase Google一键登录,AuthViewModel将获取到的token传递给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的getGoogleSignInResult(token)并挂起。远程数据源初始切换至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是异步操作,job.join()并未等待其完成。
我还尝试了修改后的代码,原本以为无法正常工作,却出乎意料能等待回调完成后返回成功结果:
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) }
疑问解答
1. 是否可以强制Firebase同步执行?
Firebase Authentication的signInWithCredential没有同步执行的官方API,它设计为异步操作(基于Task/回调),因为涉及网络请求,同步执行会阻塞线程,违反Android性能最佳实践。
不过可以使用Firebase Kotlin SDK提供的**Task.await()扩展函数**,将异步Task转换为协程挂起函数,这是更规范的实现方式,无需手动管理回调和Job:
suspend fun getGoogleSignInResult(token: String): Envelope<FirebaseUser> { return withContext(ioDispatcher) { try { val firebaseCredential = GoogleAuthProvider.getCredential(token, null) val authResult = auth.signInWithCredential(firebaseCredential).await() Envelope.Success(authResult.user!!) } catch (e: Exception) { Log.w("TEST", "signInWithCredential:failure", e) Envelope.Error(Envelope.Tag.NON_CACHED_EXCEPTION) } } }
这种方式既符合协程挂起逻辑,又能正确等待异步操作完成,避免手动处理回调的潜在问题。
2. 修改后的代码为何能正常工作?
核心原因是你误打误撞使用了Firebase Task的await()扩展函数,而非协程的Job:
- 初始代码中,
launch创建的Job仅负责执行「添加回调」这一同步操作,job.join()只会等待launch代码块执行完毕(即回调添加完成),不会等待Firebase的异步登录操作完成,因此user仍为null。 - 修改后的代码中,
withContext(ioDispatcher)的返回值是signInWithCredential返回的Task<AuthResult>,你将它赋值给了job,随后调用的job.await()是Firebase提供的挂起函数——它会挂起当前协程,直到Task对应的异步操作完成(也就是addOnCompleteListener触发的时机),此时user已经被回调赋值,自然能返回正确结果。
简单来说,你原本以为是在等待协程Job,实际是在等待Firebase异步Task完成,这才让代码正常工作。
内容的提问来源于stack exchange,提问作者mmog

