Flutter Firebase手机号认证 实现登录状态持久化问题求助
问题根因
main.dart中执行Firebase.initializeApp()时未添加await关键字,Firebase服务未完成初始化就启动应用框架,导致本地持久化的登录凭证无法被正常加载- 应用启动时固定渲染
LoginScreen,没有校验Firebase Auth当前的用户登录状态,所以每次冷启动都会直接进入登录页
注意:Firebase Auth 原生自带登录状态持久化能力,Android/iOS 平台默认会将登录凭证加密存储在本地,不需要额外引入SharedPreferences等本地存储插件,只要修复初始化和状态判断逻辑即可实现需求。
修复后完整代码
1. main.dart(核心修改文件)
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'home.dart'; import 'login.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 必须加await等待Firebase初始化完成 await Firebase.initializeApp(); runApp(const MaterialApp( home: MyApp(), debugShowCheckedModeBanner: false, )); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 监听认证状态变化,自动切换对应页面 return StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { // 等待连接完成时显示加载态 if (snapshot.connectionState == ConnectionState.waiting) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } // 存在已登录用户直接进首页 if (snapshot.hasData) { return const Home(); } // 未登录进登录页 return const LoginScreen(); }, ); } }
2. login.dart(仅替换废弃组件适配新版本Flutter,保留原有业务逻辑)
import 'package:flutter/material.dart'; import 'otp.dart'; import 'package:intl_phone_field/intl_phone_field.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { String Country = ''; final TextEditingController _controller2 = TextEditingController(); final TextEditingController _controller = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Phone Auth'), ), body: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column(children: [ Container( margin: const EdgeInsets.only(top: 60), child: const Center( child: Text( 'Phone Authentication', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 28), ), ), ), IntlPhoneField( decoration: const InputDecoration( labelText: 'Phone Number', border: OutlineInputBorder( borderSide: BorderSide(), ), ), controller: _controller, initialCountryCode: 'IN', onChanged: (phone) { debugPrint('Below is phone number'); debugPrint(phone.completeNumber); debugPrint('Country code is: ${phone.countryCode}'); Country = phone.countryCode; debugPrint(Country); }, ), ]), Container( margin: const EdgeInsets.all(10), width: double.infinity, child: TextButton( style: TextButton.styleFrom(backgroundColor: Colors.blue), onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => OTPScreen(_controller.text, Country))); }, child: const Text( 'Next', style: TextStyle(color: Colors.white), ), ), ) ], ), ); } }
调整说明:把已废弃的
FlatButton替换为官方推荐的TextButton,适配新版本Flutter,不影响原有登录逻辑。
3. otp.dart(仅做常量语法适配,保留原有验证码校验逻辑)
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'home.dart'; import 'package:pinput/pinput.dart'; class OTPScreen extends StatefulWidget { final String phone; final String countryCode; const OTPScreen(this.phone, this.countryCode, {Key? key}) : super(key: key); @override _OTPScreenState createState() => _OTPScreenState(); } class _OTPScreenState extends State<OTPScreen> { final GlobalKey<ScaffoldState> _scaffoldkey = GlobalKey<ScaffoldState>(); late String _verificationCode; final TextEditingController _pinPutController = TextEditingController(); final FocusNode _pinPutFocusNode = FocusNode(); final BoxDecoration pinPutDecoration = BoxDecoration( color: const Color.fromRGBO(43, 46, 66, 1), borderRadius: BorderRadius.circular(10.0), border: Border.all( color: const Color.fromRGBO(126, 203, 224, 1), ), ); @override Widget build(BuildContext context) { return Scaffold( key: _scaffoldkey, appBar: AppBar( title: const Text('OTP Verification'), ), body: Column( children: [ Container( margin: const EdgeInsets.only(top: 40), child: Center( child: Text( 'Verify ${widget.countryCode} ${widget.phone}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 26), ), ), ), Padding( padding: const EdgeInsets.all(30.0), child: Pinput( length: 6, onSubmitted: (pin) async { debugPrint('submit pressed'); try { await FirebaseAuth.instance .signInWithCredential(PhoneAuthProvider.credential( verificationId: _verificationCode, smsCode: pin)) .then((value) async { if (value.user != null) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const Home()), (route) => false); } }); } catch (e) { FocusScope.of(context).unfocus(); _scaffoldkey.currentState!.showSnackBar( const SnackBar(content: Text('invalid OTP'))); } }, ), ) ], ), ); } _verifyPhone() async { await FirebaseAuth.instance.verifyPhoneNumber( phoneNumber: '${widget.countryCode}${widget.phone}', verificationCompleted: (PhoneAuthCredential credential) async { await FirebaseAuth.instance .signInWithCredential(credential) .then((value) async { if (value.user != null) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const Home()), (route) => false); } }); }, verificationFailed: (FirebaseAuthException e) { debugPrint(e.message); }, codeSent: (String verficationID, int? resendToken) { setState(() { _verificationCode = verficationID; }); }, codeAutoRetrievalTimeout: (String verificationID) { setState(() { _verificationCode = verificationID; }); }, timeout: const Duration(seconds: 120)); } @override void initState() { super.initState(); _verifyPhone(); } }
4. home.dart(保留原有登出逻辑,无需修改核心功能)
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'login.dart'; class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @override _HomeState createState() => _HomeState(); } class _HomeState extends State<Home> { late String uid; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Home'), actions: [ IconButton( icon: const Icon(Icons.logout), onPressed: () async { await FirebaseAuth.instance.signOut(); Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => const LoginScreen()), (route) => false); }, ) ], ), body: Center( child: Text(uid), ), ); } @override void initState() { super.initState(); uid = FirebaseAuth.instance.currentUser!.uid; } }
效果说明
- 替换代码后重新运行应用,用户首次登录成功后,后续杀死应用重启、冷启动都会直接进入首页,不会回到登录页
- 只有用户主动点击右上角登出按钮,调用
signOut()方法清除本地凭证后,才会返回登录页 - 启动阶段的加载指示器可以避免应用初始化时出现白屏问题
内容的提问来源于stack exchange,提问作者Ankit Kumar
相关产品推荐
相关产品推荐

