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

Flutter热重载触发应用重启问题求助

Flutter热重载时应用回到初始状态的解决方案

问题原因分析

  1. 路由实例重复创建:MyApp的build方法中每次都会新建AppRouter实例,导致热重载时MaterialApp.router的routerConfig更新,路由系统重置,导航栈丢失,回到初始路由(Splash页面)。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:42:02