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

Flutter中Bloc+Provider热重启后ConnectionState两次变更问题求助

解决Bloc+Provider热重启时ConnectionState重复变更及引导页持久化问题

嘿,我来帮你分析下问题出在哪,以及怎么解决:

首先,你遇到的热重启时ConnectionState两次变更,核心原因有两个:

  1. 你的FutureBuilder的future参数是value.bloc.checkSkipped(),每次Consumer触发重建(比如热重启),这个方法都会被重新调用,生成一个新的Future,导致FutureBuilder再次经历waiting→done的流程,所以会打印两次状态。
  2. checkSkipped方法里同时做了两件事:既通过add(IntroEvents.ReadLocalStorage)让Bloc读取本地存储,又自己直接调用await _prefsManager.getValue,这属于重复操作,而且这两个操作的结果可能不同步,进一步加剧了状态混乱。

另外,你当前的代码也没实现“跳过引导页后不再显示”的核心逻辑——FutureBuilder里始终返回SplashScreen,根本没根据snapshot.data判断跳转目标。

下面是分步解决方案:


1. 重构Bloc逻辑,移除重复操作

让Bloc单独负责状态管理,初始化时自动读取本地存储,不需要额外的checkSkipped方法:

enum IntroEvents {
  ReadLocalStorage,
  SetIntroSkipped,
}

class IntroBloc extends Bloc<IntroEvents, bool> {
  final PrefsManager _prefsManager = PrefsManager.instance;

  // 构造函数里自动触发读取本地存储的事件
  IntroBloc() {
    add(IntroEvents.ReadLocalStorage);
  }

  @override
  bool get initialState => false;

  @override
  Stream<bool> mapEventToState(IntroEvents event) async* {
    switch (event) {
      case IntroEvents.SetIntroSkipped:
        await _prefsManager.setValue(PrefTypes.Bool, "skippedIntro", true);
        yield true;
        break;
      case IntroEvents.ReadLocalStorage:
        // 处理本地存储无值的情况,默认返回false(未跳过)
        final skipped = await _prefsManager.getValue(PrefTypes.Bool, "skippedIntro") ?? false;
        yield skipped;
        break;
      default:
        print("Unexpected IntroEvent");
    }
  }
}

2. 完善Provider的生命周期管理

给Bloc添加销毁逻辑,避免内存泄漏:

class IntroProvider with ChangeNotifier {
  late final IntroBloc _bloc;

  IntroProvider(){
    print("IntroProvider initialized");
    _bloc = IntroBloc();
  }

  IntroBloc get bloc => _bloc;

  @override
  void dispose() {
    _bloc.close(); // 必须关闭Bloc释放资源
    super.dispose();
  }
}

3. 用BlocBuilder替代FutureBuilder,监听状态变更

直接通过Bloc的状态流驱动UI,避免Future重复执行:

void main() => runApp(StatsApp());

class StatsApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (context) => IntroProvider(),
        )
      ],
      child: Consumer<IntroProvider>(builder: (context, provider, child) {
        return MaterialApp(
          home: BlocBuilder<IntroBloc, bool>(
            bloc: provider.bloc,
            builder: (context, isSkipped) {
              // 根据Bloc状态决定显示哪个页面
              if (isSkipped) {
                return HomeScreen(); // 替换成你的主页
              } else {
                return IntroScreen(
                  // 跳过引导页的回调,触发Bloc更新状态
                  onSkip: () {
                    provider.bloc.add(IntroEvents.SetIntroSkipped);
                  },
                );
              }
            },
          ),
        );
      }),
    );
  }
}

额外注意点

  • 确保PrefsManager的getValue方法在没有存储值时返回null,我们已经在Bloc里处理了这个情况,默认返回false。
  • 热重启时Flutter会重新运行main函数,Provider和Bloc会重新创建,但因为我们读取了本地存储,所以状态会自动恢复,不会重复显示引导页。

这样修改后,不仅能解决ConnectionState重复变更的问题,还能完美实现“跳过引导页一次后不再显示”的需求。

内容的提问来源于stack exchange,提问作者user1445685

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 15:13:10