Kotlin Clean架构下Firebase电话认证Recaptcha及Error Code39问题
在Clean架构下实现Firebase手机号认证的问题解决
问题描述
我在Kotlin中采用Clean架构实现Firebase Phone Authentication时,遇到两个核心问题:
- 未在
PhoneAuthOptions构建器中添加Activity,触发错误:'App verification failed - a valid Activity is required to complete the Recaptcha flow' - 尝试规避传递Activity到ViewModel后,出现新错误:
[SmsRetrieverHelper] SMS validation code request failed: unknown status code: 17499 Error Code:39 2024-08-22 16:40:42.831 7414-7414 ContentValues com.example.poputka W onVerificationFailed com.google.firebase.FirebaseException: An internal error occurred. [ Error Code:39 ]
已在Firebase项目中添加SHA-256和SHA-1指纹,本以为无需Recaptcha,但实际仍触发相关校验。同时不想向ViewModel传递Activity引用(违反Clean架构规范),求可行解决方案。
现有实现代码
class AuthenticationDataSourceImpl @Inject constructor(private val auth: FirebaseAuth) : AuthenticationDataSource { override suspend fun sendVerificationCode(phoneNumber: String): AuthFirebaseResult = suspendCancellableCoroutine { continuation -> val options = PhoneAuthOptions.newBuilder(auth) .setPhoneNumber(phoneNumber) .setTimeout(60L, java.util.concurrent.TimeUnit.SECONDS) .setCallbacks(object : OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(credential: PhoneAuthCredential) { Log.d(TAG, "onVerificationCompleted:$credential") if (continuation.isActive) { continuation.resume(AuthFirebaseResult.PhoneNumberVerified(credential) ) } } override fun onVerificationFailed(e: FirebaseException) { Log.w(TAG, "onVerificationFailed", e) if (continuation.isActive) { continuation.resume(AuthFirebaseResult.Exception(e)) } } override fun onCodeSent( verificationId: String, token: PhoneAuthProvider.ForceResendingToken, ) { Log.d(TAG, "onCodeSent:$verificationId") if (continuation.isActive) { continuation.resume(AuthFirebaseResult.VerificationCodeSent(verificationId)) } } }) .build() verifyPhoneNumber(options) continuation.invokeOnCancellation { // Handle the cancellation if necessary } } override suspend fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential): NetworkResult<AuthResult> = suspendCancellableCoroutine { continuation -> auth.signInWithCredential(credential) .addOnCompleteListener { task -> if (task.isSuccessful) { // Sign in success, update UI with the signed-in user's information Log.d("TAG", "signInWithCredential:success") val user = task.result?.user Log.d("user", user.toString()) continuation.resume(NetworkResult.Success(task.result!!)) } else { // Sign in failed, display a message and update the UI Log.w("TAG", "signInWithCredential:failure", task.exception) if (task.exception is FirebaseAuthInvalidCredentialsException) { // The verification code entered was invalid continuation.resume( NetworkResult.Exception( (task.exception as FirebaseAuthInvalidCredentialsException).cause ?: Exception() ) ) } } } } override suspend fun getCredential(verificationId: String, code: String): NetworkResult<PhoneAuthCredential> { return try { val credential = PhoneAuthProvider.getCredential(verificationId, code) NetworkResult.Success(credential) } catch (e: Exception) { NetworkResult.Exception(e) } } }
解决方案
1. 明确Firebase认证的验证逻辑
添加SHA指纹只是启用自动短信检索的前提,但Firebase会根据设备环境(如模拟器、陌生设备)或风控策略触发Recaptcha校验,必须提供Activity上下文来加载Recaptcha的WebView弹窗,无法完全绕过。
2. 不违反Clean架构的Activity传递方案
不要让ViewModel或DataSource持有Activity引用,而是在**UI层(Fragment/Activity)**发起请求时传入,通过UseCase和ViewModel中转:
调整实现步骤:
- 修改
AuthenticationDataSource的sendVerificationCode方法签名,新增Activity参数:override suspend fun sendVerificationCode(phoneNumber: String, activity: Activity): AuthFirebaseResult = suspendCancellableCoroutine { continuation -> val options = PhoneAuthOptions.newBuilder(auth) .setPhoneNumber(phoneNumber) .setTimeout(60L, java.util.concurrent.TimeUnit.SECONDS) .setActivity(activity) // 添加上Activity引用 .setCallbacks(...) // 原有回调逻辑不变 .build() verifyPhoneNumber(options) // 其余取消逻辑不变 } - 在UseCase和ViewModel中同步修改方法签名,仅作为参数中转,不持有Activity引用
- 在Fragment/Activity中调用时传入
this(Activity)或requireActivity()(Fragment),并确保在生命周期内完成认证流程
3. 修复Error Code:39错误
该错误通常由以下原因导致,逐一排查:
- SHA指纹配置错误:检查Firebase控制台项目设置,确认添加的是当前构建变体(debug/release)的SHA-1和SHA-256指纹
- 包名不匹配:核对Firebase控制台注册的应用包名与项目
build.gradle中的包名完全一致 - 依赖缺失:确保添加了短信检索服务依赖到
app/build.gradle:implementation "com.google.android.gms:play-services-auth-api-phone:18.0.1" - 网络问题:确认设备能正常访问Firebase服务,可切换网络测试
4. 额外优化:避免内存泄漏
- 在UI层使用
viewLifecycleOwner.lifecycleScope启动协程调用认证方法,确保协程随UI生命周期销毁 - ViewModel中不存储Activity引用,仅在调用UseCase时一次性传递
内容的提问来源于stack exchange,提问作者tereshkevich
相关产品推荐
相关产品推荐

