如何停止FirebaseAuth的Stream?避免重复触发后端数据请求
解决FirebaseAuth authStateChanges()触发API重复调用的问题
问题原因
authStateChanges()流可能多次发射用户登录状态事件(比如Firebase内部状态同步、组件重建导致StreamBuilder重新监听),而你直接在StreamBuilder的builder回调里调用CallDatabase,每次builder执行且snapshot.hasData为true时都会触发,最终导致重复调用。
解决方案
方案一:用状态标记控制仅调用一次
在页面State类中添加布尔标记,确保只有第一次检测到用户登录时才执行数据库调用,用户退出后重置标记以便下次登录重新触发。
修改后的代码:
class LoginPage extends StatefulWidget { const LoginPage({Key? key}) : super(key: key); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { bool _hasCalledDatabase = false; // 新增标记变量 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return const Center(child: Text('Incorrect Username or Password!')); } else if (snapshot.hasData) { // 仅当未执行过数据库调用时触发 if (!_hasCalledDatabase) { setState(() { _hasCalledDatabase = true; }); Email = FirebaseAuth.instance.currentUser?.email.toString(); CallDatabase(); } if (userRole == 'Education') { return eDisclaimer(); } else if (userRole == 'Packaging') { return pDisclaimer(); } else if (userRole == 'Welding') { return wDisclaimer(); } return const LoginPage(); } else { // 用户退出时重置标记 setState(() { _hasCalledDatabase = false; }); return LoginBody(); } }), ), ); } } // 以下CallAPIs、CallDatabase代码保持不变 Future CallAPIs() async { await CustomerCall(userCustomer.toString(), ApiConstants.APIKEY); await OpenProjectCall(userCustomer.toString(), ApiConstants.APIKEY); await OpenTaskCall(userCustomer.toString(), ApiConstants.APIKEY); for (var project in projectlist) { await WIPRequestCall(project.job, userCustomer.toString(), ApiConstants.APIKEY); } Future.delayed(Duration(seconds: 30), () async { await ProjectTimelineCall(userCustomer.toString(), ApiConstants.APIKEY); }); await GetPortalFiles(ApiConstants.APIKEY); } Future CallDatabase() async{ await ReadUserinDatabase(Email); userCustomer = userCustomer.replaceAll("(", ""); userCustomer = userCustomer.replaceAll(")", ""); await CallAPIs(); }
方案二:手动订阅authStateChanges()流(更灵活)
在initState中手动监听状态变化,分离状态逻辑和UI构建,避免StreamBuilder多次触发builder回调。
修改后的代码:
class LoginPage extends StatefulWidget { const LoginPage({Key? key}) : super(key: key); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { StreamSubscription? _authSubscription; bool _hasCalledDatabase = false; @override void initState() { super.initState(); // 手动订阅auth状态流 _authSubscription = FirebaseAuth.instance.authStateChanges().listen((user) { if (user != null && !_hasCalledDatabase) { setState(() { _hasCalledDatabase = true; }); Email = user.email.toString(); CallDatabase(); } else if (user == null) { // 用户退出时重置标记 setState(() { _hasCalledDatabase = false; }); } }); } @override void dispose() { _authSubscription?.cancel(); // 页面销毁时取消订阅,避免内存泄漏 super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return const Center(child: Text('Incorrect Username or Password!')); } else if (snapshot.hasData) { if (userRole == 'Education') { return eDisclaimer(); } else if (userRole == 'Packaging') { return pDisclaimer(); } else if (userRole == 'Welding') { return wDisclaimer(); } return const LoginPage(); } else { return LoginBody(); } }), ), ); } } // CallAPIs、CallDatabase代码保持不变
额外注意事项
- 如果
userRole是在CallDatabase中异步获取的,当前直接在builder中判断可能会出现空值问题,建议将userRole纳入状态管理(比如用FutureBuilder或状态变量存储) - 若页面在
CallDatabase执行过程中跳转,可添加上下文判断避免异步操作报错:if (mounted) { ... }
内容的提问来源于stack exchange,提问作者Aiden P
相关产品推荐
相关产品推荐

