Flutter集成Firebase手机号OTP认证时遭遇CustomTabs类缺失崩溃
问题场景
Flutter项目集成Firebase手机号OTP认证时,触发手机号验证流程直接崩溃,核心崩溃栈如下:
2021-01-29 14:07:36.021 32521-32521/com.example.flutter_test1 E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.flutter_test1, PID: 32521 java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/browser/customtabs/CustomTabsIntent$Builder; at com.google.firebase.auth.internal.RecaptchaActivity.zza(com.google.firebase:firebase-auth@@20.0.1:13) at com.google.android.gms.internal.firebase-auth-api.zzth.zzb(com.google.firebase:firebase-auth@@20.0.1:7) Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.browser.customtabs.CustomTabsIntent$Builder" on path: DexPathList[[zip file "/data/app/com.example.flutter_test1-VMY7lKsstWxY71zd6Q5xmA==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.flutter_test1-VMY7lKsstWxY71zd6Q5xmA==/lib/arm64, ...]]
崩溃根因
Firebase Auth 20.0.1版本的OTP流程依赖ReCaptcha人机校验,该校验需要调用AndroidX Browser库提供的CustomTabsIntent类打开内置浏览器完成验证,当前项目未引入该依赖,直接触发类找不到的崩溃。
修复步骤
- 引入缺失的AndroidX Browser依赖
打开android/app/build.gradle文件,在dependencies代码块中添加如下配置:
dependencies { // 原有其他依赖保留 implementation 'androidx.browser:browser:1.4.0' }- 引入缺失的AndroidX Browser依赖
- 补充Release包混淆规则
如果项目开启了代码混淆,需在android/app/proguard-rules.pro中添加规则,避免相关类被混淆删除:
-keep class androidx.browser.** { *; }- 补充Release包混淆规则
- 清理缓存重新构建
依次执行终端命令,避免旧缓存导致依赖不生效:
重新运行项目即可解决崩溃问题。flutter clean flutter pub get- 清理缓存重新构建
现有OTP代码的问题修正
原实现代码存在以下逻辑错误,会导致即使解决崩溃也无法正常完成OTP验证:
- 错误1:
codeSent回调中硬编码smsCode为空字符串直接调用登录。该回调是短信验证码发送成功后触发的逻辑,需要弹出输入框让用户填写收到的6位验证码,再将用户输入的验证码传入PhoneAuthProvider.credential方法生成凭证,原实现传空值必然验证失败。 - 错误2:
timeout设置为10秒过短。Android端OTP自动检索的默认超时时间为60秒,过短的超时会直接触发codeAutoRetrievalTimeout回调,打断自动检索流程。 - 错误3:
_verificationId赋值逻辑不完整。codeAutoRetrievalTimeout回调中需要将最新的verificationId赋值给全局变量,避免后续用户手动输入验证码时使用过期的ID。
修正后的核心代码示例:
Future verifyPhoneNumber(BuildContext context) async { FirebaseAuth auth = FirebaseAuth.instance; // 自动验证完成(部分支持SIM卡检索的场景下会自动识别验证码完成登录) final PhoneVerificationCompleted verificationCompleted = (PhoneAuthCredential credential) async { await auth.signInWithCredential(credential); // 登录成功后跳转业务页面 }; // 验证失败回调 final PhoneVerificationFailed verificationFailed = (FirebaseAuthException e) { debugPrint("OTP验证失败: ${e.message}"); }; // 验证码发送成功回调 final PhoneCodeSent codeSent = (String verificationId, int? forceResendingToken) async { // 弹出输入框,让用户输入收到的验证码 String? smsCode = await showDialog<String>( context: context, builder: (context) => AlertDialog( content: TextField( keyboardType: TextInputType.number, maxLength: 6, onChanged: (value) => smsCode = value, ), actions: [TextButton(onPressed: () => Navigator.pop(context, smsCode), child: const Text("确定"))], ), ); if (smsCode == null || smsCode.isEmpty) return; // 用用户输入的验证码生成凭证登录 PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: verificationId, smsCode: smsCode, ); await auth.signInWithCredential(credential); }; // 自动检索超时回调 final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = (String verificationId) { // 保存当前verificationId,避免后续使用过期ID _verificationId = verificationId; }; try { await auth.verifyPhoneNumber( phoneNumber: _mobileNumberController.text.trim(), // 超时时间设置为60秒,符合系统默认要求 timeout: const Duration(seconds: 60), verificationCompleted: verificationCompleted, verificationFailed: verificationFailed, codeSent: codeSent, codeAutoRetrievalTimeout: codeAutoRetrievalTimeout, ); } catch (e) { debugPrint("调用OTP接口异常: $e"); } }
内容的提问来源于stack exchange,提问作者Amrutha Desai
相关产品推荐
相关产品推荐

