You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Firebase手机号OTP登录遇E.164格式错误求助

Flutter IntlPhoneField + Firebase OTP登录:手机号E.164格式错误排查与解决

问题背景

使用Flutter的IntlPhoneField组件结合Firebase实现手机号+OTP登录时,调用verifyPhoneNumber接口收到错误提示:

提供的手机号格式不正确,请输入可解析为E.164格式的手机号。E.164格式手机号需按[+|国家码][包含区号的用户号码]格式填写。无效格式。

错误原因分析

  1. 拼接顺序错误:当前代码将用户输入的手机号phoneNumber和国家码countryCode直接拼接,但顺序颠倒,且国家码缺少必填的+前缀,不符合E.164格式要求。
  2. 未利用组件自带能力:IntlPhoneField组件本身提供了直接获取标准E.164格式号码的属性,无需手动拼接。

解决方案

方案1:使用IntlPhoneField自带的标准号码(推荐)

IntlPhoneField的回调参数phone为PhoneNumber类型,其completeNumber属性直接返回带+前缀的完整E.164格式号码,无需手动处理拼接逻辑。

修改步骤:

  1. 调整手机号输入组件的onChanged回调,直接存储完整标准号码
  2. 简化Provider调用,仅传递完整号码
  3. 移除Provider方法中的国家码参数,直接使用完整号码调用Firebase接口

方案2:修复手动拼接逻辑(不推荐)

若坚持手动拼接,需确保:

  • 国家码以+开头
  • 拼接顺序为+国家码 + 纯数字手机号(手机号需去掉前导0)

修改后的代码示例

1. 手机号输入组件(方案1)

IntlPhoneField(
  controller: phoneController,
  showCountryFlag: false,
  decoration: InputDecoration(
    isDense: true,
    enabledBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: AppColors.greyColor),
    ),
    focusedBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: AppColors.greyColor),
    ),
  ),
  initialCountryCode: 'PK',
  onChanged: (phone) {
    // 直接获取符合E.164标准的完整号码
    setState(() {
      fullPhoneNumber = phone.completeNumber;
    });
  },
)

2. 调用Provider代码(方案1)

if (_formKey.currentState!.validate()){
  AddNewDialogBox.dialog(context, true);
  context
      .read<LoginPhoneProvider>()
      .phoneSignInn(context, fullPhoneNumber);
  print(fullPhoneNumber);
}

3. Provider中获取OTP代码(方案1)

Future<void> phoneSignInn(
  BuildContext context,
  String fullPhoneNumber,
) async {
  TextEditingController codeController = TextEditingController();
  if (kIsWeb) {
    ConfirmationResult result =
    await _auth.signInWithPhoneNumber(fullPhoneNumber);

    showOTPDialog(
      phoneNumber: fullPhoneNumber,
      codeController: codeController,
      context: context,
      onPressed: () async {
        PhoneAuthCredential credential = PhoneAuthProvider.credential(
          verificationId: result.verificationId,
          smsCode: codeController.text.trim(),
        );

        await _auth.signInWithCredential(credential);
        Navigator.of(context).pop();
      },
    );
  } else {
    await _auth.verifyPhoneNumber(
      phoneNumber: fullPhoneNumber,
      verificationCompleted: (PhoneAuthCredential credential) async {
        await _auth.signInWithCredential(credential);
      },
      verificationFailed: (e) {
        showSnackBar(context, e.message!);
      },
      codeSent: ((String verificationId, int? resendToken) async {
        showOTPDialog(
          phoneNumber: fullPhoneNumber,
          codeController: codeController,
          context: context,
          onPressed: () async {
            PhoneAuthCredential credential = PhoneAuthProvider.credential(
              verificationId: verificationId,
              smsCode: codeController.text.trim(),
            );

            await _auth.signInWithCredential(credential);
            Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => const DriverSginUpScreen()));
          },
        );
      }),
      codeAutoRetrievalTimeout: (String verificationId) {},
    );
  }
}

4. OTP弹窗代码(无需修改)

void showOTPDialog({
  required BuildContext context,
  required TextEditingController codeController,
  required VoidCallback onPressed,
  required String phoneNumber,
}) {
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (context) => SizedBox(
      width: double.infinity,
      child: AlertDialog(
        title: Text("Enter 6 Digit OTP send to\n$phoneNumber"),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            PinCodeTextField(
              pinTheme: PinTheme(
                shape: PinCodeFieldShape.box,
                borderRadius: BorderRadius.circular(5),
                fieldHeight: 60,
                fieldWidth: 30,
                activeFillColor: Colors.white,
              ),
              appContext: context,
              length: 6,
              controller: codeController,
              onChanged: (value) {
                print(value);
              },
            ),
          ],
        ),
        actions: <Widget>[
          TextButton(
            onPressed: onPressed,
            child: const Text("Done"),
          )
        ],
      ),
    ),
  );
}

关键注意事项

  • 确保用户输入的手机号无多余空格、符号,IntlPhoneField会自动处理格式校验
  • Firebase手机号验证严格遵循E.164标准,号码必须以+开头,无任何非数字字符

内容的提问来源于stack exchange,提问作者Rashid Ali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:20:28