Flutter认证场景:如何结合StreamBuilder与异步代码且不重建LoadingScreen
问题描述
我正在开发一款Flutter应用,使用Provider结合Firebase Auth实现认证功能。已编写处理认证状态的包装器代码如下:
StreamBuilder<GoogleUser?> authenticationWrapper(BuildContext context) { // Firebase Auth instance final googleAuth = Provider.of<FirebaseAuthApi>(context); return StreamBuilder<GoogleUser?>( stream: googleAuth.user, builder: (_, AsyncSnapshot<GoogleUser?> snapshot) { // Google auth stream active if (snapshot.connectionState == ConnectionState.active) { final GoogleUser? user = snapshot.data; // User logged in if (user != null) { return const HomeScreen(); } // No user logged in else { return const LoginScreen(); } } // Google auth stream inactive else { return const LoadingScreen(); } } ); }
需求是:在返回HomeScreen前添加异步检查判断用户是否首次登录,但不想重建LoadingScreen。之前用FutureBuilder的方案会强制重建LoadingScreen,希望得到避免该问题的方案,或者让StreamBuilder返回值而非Widget的方法。
解决方案
方案一:在HomeScreen内部处理首次登录检查
将异步检查逻辑内嵌到HomeScreen中,让StreamBuilder仅负责根据认证状态切换页面,不会触发全局LoadingScreen重建。检查过程中只在HomeScreen内部显示局部加载状态。
代码示例:
class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { bool _isCheckingFirstLogin = true; bool _isFirstLogin = false; @override void initState() { super.initState(); _checkFirstLogin(); } Future<void> _checkFirstLogin() async { // 替换为你的首次登录检查逻辑,比如从Firestore读取用户标记 final user = FirebaseAuth.instance.currentUser; if (user != null) { final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get(); _isFirstLogin = !doc.exists; } setState(() { _isCheckingFirstLogin = false; }); } @override Widget build(BuildContext context) { if (_isCheckingFirstLogin) { // 局部加载,不触发全局LoadingScreen重建 return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (_isFirstLogin) { // 首次登录引导页 return const OnboardingScreen(); } // 正常首页内容 return const Scaffold( body: Center(child: Text('Home Screen')), ); } }
方案二:扩展Auth Provider,在Stream中合并首次登录状态
修改FirebaseAuthApi,让用户流同时返回用户信息和首次登录检查结果,将整个流程整合到单个Stream中,避免额外的FutureBuilder触发LoadingScreen重建。
代码示例:
// 定义包含认证状态的合并数据类 class AuthState { final GoogleUser? user; final bool? isFirstLogin; final bool isLoading; AuthState({ required this.user, this.isFirstLogin, required this.isLoading, }); } // 修改FirebaseAuthApi,生成包含首次登录状态的流 class FirebaseAuthApi with ChangeNotifier { final FirebaseAuth _auth = FirebaseAuth.instance; late Stream<AuthState> _userStateStream; FirebaseAuthApi() { _userStateStream = _auth.authStateChanges().asyncMap((user) async { if (user == null) { return AuthState(user: null, isFirstLogin: null, isLoading: false); } // 执行首次登录检查 final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get(); final isFirstLogin = !doc.exists; return AuthState(user: user, isFirstLogin: isFirstLogin, isLoading: false); }).startWith(const AuthState(user: null, isFirstLogin: null, isLoading: true)); } Stream<AuthState> get userState => _userStateStream; } // 更新认证包装器 StreamBuilder<AuthState> authenticationWrapper(BuildContext context) { final googleAuth = Provider.of<FirebaseAuthApi>(context); return StreamBuilder<AuthState>( stream: googleAuth.userState, builder: (_, AsyncSnapshot<AuthState> snapshot) { if (snapshot.connectionState == ConnectionState.waiting || snapshot.data?.isLoading == true) { return const LoadingScreen(); } final authState = snapshot.data; if (authState?.user == null) { return const LoginScreen(); } // 根据首次登录状态返回对应页面 return authState?.isFirstLogin == true ? const OnboardingScreen() : const HomeScreen(); }, ); }
方案三:用StatefulWidget封装StreamBuilder与检查逻辑
创建StatefulWidget保存当前用户和检查状态,仅在Stream未激活时显示全局LoadingScreen,用户登录后的异步检查仅显示局部加载,避免重建全局加载页。
代码示例:
class AuthWrapper extends StatefulWidget { const AuthWrapper({super.key}); @override State<AuthWrapper> createState() => _AuthWrapperState(); } class _AuthWrapperState extends State<AuthWrapper> { bool _isCheckingFirstLogin = false; bool _isFirstLogin = false; GoogleUser? _currentUser; @override Widget build(BuildContext context) { final googleAuth = Provider.of<FirebaseAuthApi>(context); return StreamBuilder<GoogleUser?>( stream: googleAuth.user, builder: (_, AsyncSnapshot<GoogleUser?> snapshot) { if (snapshot.connectionState != ConnectionState.active) { return const LoadingScreen(); } final user = snapshot.data; if (user == null) { // 重置检查状态 setState(() { _isCheckingFirstLogin = false; _currentUser = null; }); return const LoginScreen(); } // 用户未切换时复用检查结果 if (_currentUser?.uid == user.uid && !_isCheckingFirstLogin) { return _isFirstLogin ? const OnboardingScreen() : const HomeScreen(); } // 新用户或切换用户,执行检查 _currentUser = user; _checkFirstLogin(user); // 显示局部加载 return const Scaffold( body: Center(child: CircularProgressIndicator()), ); }, ); } Future<void> _checkFirstLogin(GoogleUser user) async { setState(() { _isCheckingFirstLogin = true; }); // 异步检查逻辑 final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get(); setState(() { _isFirstLogin = !doc.exists; _isCheckingFirstLogin = false; }); } }
内容的提问来源于stack exchange,提问作者Go-At Solutions
相关产品推荐
相关产品推荐

