You need to enable JavaScript to run this app. Flutter集成Firebase手机号OTP认证时遭遇CustomTabs类缺失崩溃
优惠活动
大模型
产品
解决方案
定价
更多

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类打开内置浏览器完成验证,当前项目未引入该依赖,直接触发类找不到的崩溃。

修复步骤
    1. 引入缺失的AndroidX Browser依赖
      打开android/app/build.gradle文件,在dependencies代码块中添加如下配置:
    dependencies {
        // 原有其他依赖保留
        implementation 'androidx.browser:browser:1.4.0'
    }
    
    1. 补充Release包混淆规则
      如果项目开启了代码混淆,需在android/app/proguard-rules.pro中添加规则,避免相关类被混淆删除:
    -keep class androidx.browser.** { *; }
    
    1. 清理缓存重新构建
      依次执行终端命令,避免旧缓存导致依赖不生效:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.07 04:45:03