Flutter加载页面跳转无限循环问题排查求助
问题描述
我给Flutter应用做了带启动动画的加载页,要求4秒计时器结束且数据加载完成后才跳转到首页。实现方式:
- 在
initState里用Future.delayed实现计时器 - 通过Riverpod的Stream Provider加载数据,完成后初始化另一个Provider
但新用户注册后,出现加载页跳首页后立即跳回加载页的无限循环,推测是注册数据还没写入数据库时,currentUserProvider就尝试获取数据。附上相关代码请求排查:
认证状态与当前用户Provider代码
final authStateProvider = StreamProvider<User?>((ref) { final firebaseApp = ref .watch(firebaseProvider) .value; if (firebaseApp == null) { // Firebase is not initialized yet return const Stream.empty(); } final auth = FirebaseAuth.instanceFor(app: firebaseApp); return auth.authStateChanges(); }); final currentUserProvider = StreamProvider<CurrentUser>((ref){ final authState = ref.watch(authStateProvider); print("Current User Provider"); return authState.when( data: (user) { if (user == null) { return const Stream.empty(); } else { String uid = user.uid; // User is logged in, fetch user data from Firestore final firestore = FirebaseFirestore.instance; return firestore.collection('users').doc(uid).snapshots().map((snapshot) { //parse data return currentUser; }); } }, loading: () => const Stream.empty(), error: (_, __) => const Stream.empty(), ); });
加载页面代码
class LoadingScreen extends ConsumerStatefulWidget { LoadingScreen({super.key}); static const routeName = '/load'; @override ConsumerState<ConsumerStatefulWidget> createState() => _LoadingScreenState(); } class _LoadingScreenState extends ConsumerState<LoadingScreen> { bool _dataLoaded = false; bool _timerFinished = false; bool _loading = false; final int loadingTime = 4; @override void initState() { super.initState(); //this initializes the timer WidgetsBinding.instance.addPostFrameCallback((_) { _loadTimer(); }); } @override Widget build(BuildContext context) { var currentUser = ref.watch(currentUserProvider); if (FirebaseAuth.instance.currentUser!.emailVerified == false) { FirebaseAuth.instance.currentUser!.reload(); } if (_dataLoaded && _timerFinished) { Future.delayed(Duration.zero, () { Navigator.pushReplacementNamed(context, Home.routeName); }); } //this should load the data. I want this ptocess to start as soon as the current user provider is ready _loadData(); //heres the ui return Container(); } Future<void> _loadTimer() async { await Future.delayed(Duration(seconds: loadingTime)); setState(() { _timerFinished = true; print("Timer finished"); }); } Future<void> _loadData() async { print("Load Data"); ref.watch(currentUserProvider).when( data: (user) async { print("User: $user"); if(user == null){ return; } //this is to make sure the datat here is only loadet once if (!_loading) { _loading = true; ref .read(nutritionPlanProvider) .init(ref, user.mealPlan ?? {}, user.nutritionPlan, context); setState(() { _dataLoaded = true; }); } }, loading: () {}, error: (error, stackTrace) { CustomSnackBar.show( context, AppLocalizationUtil.of(context).error_fetching_user_data, ); print('Error: $error'); print('Stacktrace: $stackTrace'); }); } }
问题排查与修复方案
核心问题分析
- currentUserProvider的空Stream处理不当:新用户注册后,Auth状态会立即返回已登录用户,但Firestore的用户文档还未创建,此时
snapshots()返回不存在的快照,原代码直接解析返回currentUser(大概率是未初始化的空值),导致Provider状态异常,触发页面重复重建。 - 加载页跳转逻辑无防重复机制:
_dataLoaded和_timerFinished的状态依赖setState,但currentUserProvider后续更新(比如用户文档创建完成)会触发build重复执行,可能导致跳转逻辑被多次触发。 - 直接调用FirebaseAuth实例导致状态不同步:加载页里直接用
FirebaseAuth.instance.currentUser,而非通过authStateProvider获取,会出现本地缓存与Provider状态不一致的情况。 - _loadData在build中重复调用:build方法每次重建都会执行
_loadData,即使有_loading标志,也可能因Provider状态变化引发逻辑冲突。
具体修复步骤
1. 修复currentUserProvider的快照处理
明确处理文档不存在的情况,返回合法的空值而非空Stream,让上层逻辑能识别新用户的状态:
final currentUserProvider = StreamProvider<CurrentUser?>((ref){ final authState = ref.watch(authStateProvider); print("Current User Provider"); return authState.when( data: (user) { if (user == null) { return const Stream.value(null); // 返回明确的null,替代空Stream } else { String uid = user.uid; final firestore = FirebaseFirestore.instance; return firestore.collection('users').doc(uid).snapshots().map((snapshot) { if (!snapshot.exists) { // 新用户文档未创建,返回null return null; } // 正常解析用户数据 return CurrentUser.fromSnapshot(snapshot); // 替换成你的实际解析逻辑 }); } }, loading: () => const Stream.value(null), error: (_, __) => const Stream.value(null), ); });
2. 重构加载页状态逻辑,避免重复跳转
把数据监听和跳转逻辑移到ref.listen中,用_hasNavigated标志防止重复跳转:
class _LoadingScreenState extends ConsumerState<LoadingScreen> { bool _timerFinished = false; bool _hasNavigated = false; // 新增防重复跳转标志 final int loadingTime = 4; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _loadTimer(); }); } @override void didChangeDependencies() { super.didChangeDependencies(); // 监听currentUserProvider的状态变化 ref.listen<AsyncValue<CurrentUser?>>(currentUserProvider, (previous, next) { next.when( data: (user) { if (user != null && _timerFinished && !_hasNavigated) { _initAndNavigate(user); } }, loading: () {}, error: (error, stackTrace) { CustomSnackBar.show( context, AppLocalizationUtil.of(context).error_fetching_user_data, ); print('Error: $error'); print('Stacktrace: $stackTrace'); }, ); }); } @override Widget build(BuildContext context) { // 启动动画UI return Container(); } Future<void> _loadTimer() async { await Future.delayed(Duration(seconds: loadingTime)); setState(() { _timerFinished = true; print("Timer finished"); }); // 计时器结束后检查是否已获取有效用户 final currentUser = ref.read(currentUserProvider).value; if (currentUser != null && !_hasNavigated) { _initAndNavigate(currentUser); } } void _initAndNavigate(CurrentUser user) { if (_hasNavigated) return; _hasNavigated = true; // 初始化营养计划 ref.read(nutritionPlanProvider).init(ref, user.mealPlan ?? {}, user.nutritionPlan, context); // 跳转首页,用pushReplacement防止返回加载页 Navigator.pushReplacementNamed(context, Home.routeName); } }
3. 确保注册流程中用户文档写入完成后再跳转加载页
在注册代码里,必须等待Firestore写入用户文档的操作完成后,再导航到加载页:
// 示例注册代码 Future<void> registerUser(String email, String password, UserData userData) async { final auth = FirebaseAuth.instance; final firestore = FirebaseFirestore.instance; // 1. 创建Auth用户 UserCredential credential = await auth.createUserWithEmailAndPassword(email: email, password: password); User? user = credential.user; if (user != null) { // 2. 等待Firestore写入完成,再跳转 await firestore.collection('users').doc(user.uid).set(userData.toJson()); Navigator.pushReplacementNamed(context, LoadingScreen.routeName); } }
4. 移除加载页中直接调用FirebaseAuth的代码
原代码中FirebaseAuth.instance.currentUser!.reload()的逻辑,建议移到专门的邮箱验证页面或authStateProvider的监听中,避免在加载页的build方法中直接操作Auth实例,保证状态同步。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

