Flutter中Bloc+Provider热重启后ConnectionState两次变更问题求助
解决Bloc+Provider热重启时ConnectionState重复变更及引导页持久化问题
嘿,我来帮你分析下问题出在哪,以及怎么解决:
首先,你遇到的热重启时ConnectionState两次变更,核心原因有两个:
- 你的
FutureBuilder的future参数是value.bloc.checkSkipped(),每次Consumer触发重建(比如热重启),这个方法都会被重新调用,生成一个新的Future,导致FutureBuilder再次经历waiting→done的流程,所以会打印两次状态。 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
相关产品推荐
相关产品推荐

