Flutter Firebase手机号验证报错:无效的Verification ID
问题描述
我开发了两个页面,一个用于输入手机号,另一个用于输入发送至该手机号的验证码以完成身份验证。但即使输入正确的验证码,仍触发如下错误:
Unhandled Exception: [firebase_auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.
手机号输入页面代码
class PhoneNumberEntry extends StatefulWidget { const PhoneNumberEntry({Key? key}) : super(key: key); @override State<PhoneNumberEntry> createState() => _PhoneNumberEntryState(); } class _PhoneNumberEntryState extends State<PhoneNumberEntry> { TextEditingController _phonenumber = TextEditingController(); FirebaseAuth auth = FirebaseAuth.instance; String verificationID = ""; void loginWithPhone() async { await auth.verifyPhoneNumber( phoneNumber: _phonenumber.text, verificationCompleted: (PhoneAuthCredential credential) async { await auth.signInWithCredential(credential).then((value) { print("You are logged in successfully"); }); }, verificationFailed: (FirebaseAuthException e) { print(e.message); }, codeSent: (String verificationId, int? resendToken) async { setState(() { verificationID = verificationId; Navigator.push( context, MaterialPageRoute( builder: (context) => PhoneNumberVerification( phonenumber: _phonenumber.text, auth: auth, verificationId: verificationID, ))); }); }, codeAutoRetrievalTimeout: (String verificationId) {}, ); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox.expand( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0XFF0B1630), Color(0XFF2C496A), ]), image: DecorationImage( image: AssetImage("asset/images/SpirationDark.jpg"), fit: BoxFit.cover, )), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), child: Container( width: double.infinity, child: const Text( 'Let us', style: TextStyle( fontSize: 40, letterSpacing: 0.5, fontFamily: 'PlayfairDisplay', color: Colors.white), textAlign: TextAlign.left, ), ), ), Padding( padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), child: Container( width: double.infinity, child: const Text( 'Know Your', style: TextStyle( fontSize: 40, letterSpacing: 0.5, fontFamily: 'PlayfairDisplay', color: Colors.white, ), textAlign: TextAlign.left, ), ), ), Padding( padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0), child: Container( width: double.infinity, child: const Text( 'Mobile Number', style: TextStyle( fontSize: 40, letterSpacing: 0.5, fontFamily: 'PlayfairDisplay', color: Colors.white), textAlign: TextAlign.left, ), ), ), Padding( padding: const EdgeInsets.fromLTRB(8.0, 6.0, 8.0, 0), child: Container( width: double.infinity, child: const Text( 'To track your expenses and to use other features we need to use your mobile number.', style: TextStyle( fontSize: 14, letterSpacing: 0.5, fontFamily: 'PlayfairDisplay', color: Colors.grey), textAlign: TextAlign.left, ), ), ), Padding( padding: EdgeInsets.fromLTRB(10, 20, 10, 10), child: TextField( controller: _phonenumber, keyboardType: TextInputType.phone, textAlign: TextAlign.left, decoration: InputDecoration( hintText: "Enter your Phone Number", hintStyle: const TextStyle( fontSize: 15, color: Colors.white, fontFamily: 'SfProDisplay', ), fillColor: Colors.grey.withOpacity(0.15), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10))), style: const TextStyle( color: Color(0xFFFFFFFF), fontSize: 25, fontFamily: 'SfProDisplay', ), ), ), ElevatedButton( style: ElevatedButton.styleFrom( primary: Colors.grey, ), onPressed: () { loginWithPhone(); }, child: const Text( 'Continue', style: TextStyle( fontFamily: 'SfProRounded', fontSize: 25, color: Colors.black, ), ), ), ], )), ), ), backgroundColor: primarycolor, ); } }
验证码输入页面代码
class PhoneNumberVerification extends StatefulWidget { final String phonenumber; final FirebaseAuth auth; final String verificationId; const PhoneNumberVerification( {Key? key, required this.phonenumber, required this.auth, required this.verificationId}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<PhoneNumberVerification> { TextEditingController textEditingController = TextEditingController(); String currentText = ""; @override void initState() { super.initState(); } void verifyOTP( FirebaseAuth auth, String verificationId, String smsCode) async { PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: verificationId, smsCode: smsCode); await auth.signInWithCredential(credential).then((value) { print(value); }); } @override Widget build(BuildContext context) { double width = MediaQuery.of(context).size.width; return Scaffold( body: SizedBox.expand( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0XFF0B1630), Color(0XFF2C496A), ]), image: DecorationImage( image: AssetImage("asset/images/SpirationDark.jpg"), fit: BoxFit.cover, )), child: Center( child: Align( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Padding( padding: EdgeInsets.fromLTRB(10, 0, 0, 0), child: Align( alignment: Alignment.centerLeft, child: Text( "We've sent you", style: TextStyle( fontFamily: 'PlayfairDisplay', color: Colors.white, letterSpacing: 0.5, fontSize: 30, ), ), ), ), const Padding( padding: EdgeInsets.fromLTRB(10, 0, 0, 0), child: Align( alignment: Alignment.centerLeft, child: Text( "an OTP", style: TextStyle( fontFamily: 'PlayfairDisplay', color: Colors.white, letterSpacing: 0.5, fontSize: 30, ), ), ), ), const Padding( padding: EdgeInsets.all(10), child: Text( "enter the 4-digit code we're sent on 9692499906", style: TextStyle( fontFamily: 'SfProRounded', color: Colors.grey, letterSpacing: 0.5, fontSize: 15, ), ), ), PinCodeFields(text: currentText), const Padding( padding: EdgeInsets.fromLTRB(10, 2, 0, 0), child: Align( alignment: Alignment.centerLeft, child: Text.rich(TextSpan( text: "didnt recieve code ?", style: TextStyle( fontFamily: 'SfProRounded', color: Colors.grey, letterSpacing: 0.5, fontSize: 15, ), children: [])), )), Padding( padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Align( alignment: Alignment.centerLeft, child: SizedBox( height: 60, width: width * 0.45, child: ElevatedButton( onPressed: () { // print(widget.verificationId); verifyOTP(widget.auth, widget.verificationId, currentText); Navigator.push( context, MaterialPageRoute( builder: (context) => const Home())); }, child: const Center( child: Text("Verify"), ), style: ElevatedButton.styleFrom( primary: Colors.grey.withOpacity(0.7)), ), ), ), ) ], ), ), ), ), ), backgroundColor: primarycolor, ); } }
问题原因及修复方案
核心问题
在PhoneNumberEntry页面的codeSent回调中,你在setState里同时更新verificationID并跳转页面,但setState是异步执行的,跳转时本地的verificationID可能还未完成更新,导致传递给PhoneNumberVerification的是初始空字符串,而非Firebase返回的有效验证ID。
修复步骤
修改codeSent回调代码,直接使用回调返回的verificationId参数传递给下一个页面,无需通过setState更新本地变量:
codeSent: (String verificationId, int? resendToken) async { // 移除setState,直接使用回调返回的verificationId Navigator.push( context, MaterialPageRoute( builder: (context) => PhoneNumberVerification( phonenumber: _phonenumber.text, auth: auth, verificationId: verificationId, ))); },
额外优化建议
- 手机号格式校验:确保传递给
verifyPhoneNumber的手机号符合Firebase要求(例如带国家码:+8613xxxxxxxxx),格式错误会导致验证流程异常。 - 错误处理增强:在
verifyOTP方法中添加异常捕获,避免验证失败时崩溃,并给用户明确提示:
void verifyOTP(FirebaseAuth auth, String verificationId, String smsCode) async { try { PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: verificationId, smsCode: smsCode); UserCredential userCredential = await auth.signInWithCredential(credential); print("登录成功:${userCredential.user?.phoneNumber}"); // 仅在验证成功后跳转页面 Navigator.push( context, MaterialPageRoute(builder: (context) => const Home())); } on FirebaseAuthException catch (e) { print("验证失败:${e.message}"); // 这里可以添加弹窗提示用户验证码错误或验证ID无效 } }
- 跳转时机调整:只有在验证成功后再执行页面跳转,避免用户未完成验证就进入主页。
内容的提问来源于stack exchange,提问作者Sovit Nayak
相关产品推荐
相关产品推荐

