Flutter热重载触发应用重启问题求助
Flutter热重载时应用回到初始状态的解决方案
问题原因分析
- 路由实例重复创建:
MyApp的build方法中每次都会新建AppRouter实例,导致热重载时MaterialApp.router的routerConfig更新,路由系统重置,导航栈丢失,回到初始路由(Splash页面)。 - Bloc事件重复触发:
SplashView中直接在BaseView的bloc参数里实例化SplashBloc并添加事件,热重载时页面重建会重复创建Bloc实例并触发导航事件,导致应用重新走启动流程。
解决方案
1. 将AppRouter改为单例
修改MyApp代码,把AppRouter改为静态单例,确保热重载时路由实例不变,保留导航栈:
class MyApp extends StatelessWidget { const MyApp({super.key}); // 静态单例,只初始化一次 static final AppRouter _appRouter = AppRouter(); @override Widget build(BuildContext context) { print("started"); // started return MaterialApp.router( debugShowCheckedModeBanner: false, title: AppConstants.appName, themeMode: context.themeMode, theme: context.theme, darkTheme: context.theme, routerConfig: _appRouter.config(), scaffoldMessengerKey: GlobalKey<ScaffoldMessengerState>(), ); } }
2. 调整SplashBloc事件触发时机
修改SplashView,将Bloc事件移到onReady回调中,避免热重载重复触发:
@RoutePage() class SplashView extends StatelessWidget { const SplashView({super.key}); @override Widget build(BuildContext context) { return BaseView<SplashBloc, SplashState>( // 传入Bloc实例,不在此处直接添加事件 cubit: SplashBloc(), onReady: (bloc) { // 只在页面初始化时触发一次事件 bloc.add(SplashCheckSkippedEvent(context: context)); }, onPageBuilder: (context, cubit, state) { return Scaffold( body: Container( decoration: _containerDecoration(), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ TransferGoalLogo( theme: LogoTheme.light, width: context.dynamicWidth(0.8), ), context.emptySizedHeightNormal, const Center( child: CircularProgressIndicator(), ), ], ), ), ); }, ); } BoxDecoration _containerDecoration() { return BoxDecoration( image: DecorationImage( image: AssetImage(ImageConstants.instance.splashBackground), fit: BoxFit.cover, opacity: 0.5, ), gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black87, Colors.black54, Colors.black45, ], ), ); } }
3. 优化BaseView的Bloc生命周期管理(可选)
调整BaseView支持通过create方法创建Bloc,让Provider自动管理Bloc生命周期,避免手动传入实例的冗余问题:
typedef WidgetBuilderCallback<C extends BlocBase<S>, S> = Widget Function( BuildContext context, C cubit, S state); typedef OnReadyCallback<C> = void Function(C cubit); typedef BlocCreateCallback<C extends BlocBase<S>, S> = C Function(BuildContext context); class BaseView<C extends BlocBase<S>, S> extends StatefulWidget { final WidgetBuilderCallback<C, S> onPageBuilder; final OnReadyCallback<C>? onReady; final BlocCreateCallback<C, S>? create; final C? cubit; final BlocBase<S>? bloc; const BaseView({ super.key, required this.onPageBuilder, this.onReady, this.create, this.cubit, this.bloc, }) : assert(create != null || cubit != null || bloc != null, 'Create function, Cubit or Bloc must be provided'); @override State<BaseView<C, S>> createState() => _BaseViewState<C, S>(); } class _BaseViewState<C extends BlocBase<S>, S> extends State<BaseView<C, S>> { late final C _cubit; bool _isReadyCalled = false; @override void initState() { super.initState(); if (widget.create != null) { _cubit = widget.create!(context); } else if (widget.cubit != null) { _cubit = widget.cubit!; } else { _cubit = widget.bloc as C; } _callOnReady(); } @override void didUpdateWidget(covariant BaseView<C, S> oldWidget) { super.didUpdateWidget(oldWidget); // 仅当Bloc实例变化时重新调用onReady if (widget.create != null || _cubit != oldWidget.cubit) { _cubit = widget.create != null ? widget.create!(context) : widget.cubit!; _callOnReady(); } } void _callOnReady() { if (!_isReadyCalled) { widget.onReady?.call(_cubit); _isReadyCalled = true; } } @override void dispose() { _cubit.close(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider<C>( create: (context) => _cubit, child: BlocBuilder<C, S>( builder: (context, state) => widget.onPageBuilder(context, _cubit, state), ), ); } }
使用优化后的BaseView,SplashView可以改为:
@RoutePage() class SplashView extends StatelessWidget { const SplashView({super.key}); @override Widget build(BuildContext context) { return BaseView<SplashBloc, SplashState>( create: (context) => SplashBloc(), onReady: (bloc) { bloc.add(SplashCheckSkippedEvent(context: context)); }, onPageBuilder: (context, cubit, state) { // 页面代码保持不变 }, ); } }
验证修改
完成上述修改后执行热重载:
- 路由栈会保留当前页面,不会回到Splash
- Bloc不会重复创建和触发事件,避免不必要的导航动作
内容的提问来源于stack exchange,提问作者Emir Bolat
相关产品推荐
相关产品推荐

