You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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');
        });
  }
}

问题排查与修复方案

核心问题分析

  1. currentUserProvider的空Stream处理不当:新用户注册后,Auth状态会立即返回已登录用户,但Firestore的用户文档还未创建,此时snapshots()返回不存在的快照,原代码直接解析返回currentUser(大概率是未初始化的空值),导致Provider状态异常,触发页面重复重建。
  2. 加载页跳转逻辑无防重复机制:_dataLoaded和_timerFinished的状态依赖setState,但currentUserProvider后续更新(比如用户文档创建完成)会触发build重复执行,可能导致跳转逻辑被多次触发。
  3. 直接调用FirebaseAuth实例导致状态不同步:加载页里直接用FirebaseAuth.instance.currentUser,而非通过authStateProvider获取,会出现本地缓存与Provider状态不一致的情况。
  4. _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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:22:05