Flutter中BLoC结合Timer等需释放资源场景的状态处理疑问
解决方案:同时处理BLoC API状态与Timer资源释放
方案1:页面State中管理Timer,联动BLoC
直接在页面的StatefulWidget里维护Timer,是最轻量化的方式,无需额外创建BLoC。核心是在dispose中取消Timer,同时在Timer回调里先检查页面是否mounted,避免调用已销毁的context。
示例代码:
class YourPage extends StatefulWidget { const YourPage({super.key}); @override State<YourPage> createState() => _YourPageState(); } class _YourPageState extends State<YourPage> { Timer? _timer; final Stopwatch _stopwatch = Stopwatch(); @override void initState() { super.initState(); _startTimer(); // 触发API请求 context.read<ApiBloc>().add(FetchData()); } void _startTimer() { _stopwatch.start(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (!mounted) { timer.cancel(); return; } // 通知BLoC更新计时状态 context.read<ApiBloc>().add(UpdateTimer(_stopwatch.elapsed)); }); } @override void dispose() { _timer?.cancel(); _stopwatch.stop(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: BlocBuilder<ApiBloc, ApiState>( builder: (context, state) { if (state is ApiLoading) { return const CircularProgressIndicator(); } else if (state is ApiLoadedWithTimer) { return Text('计时: ${state.elapsed.inSeconds}s'); } return const SizedBox(); }, ), ); } }
方案2:扩展现有API BLoC,内置Timer管理
若希望Timer逻辑与BLoC绑定,可给现有ApiBloc添加Timer相关的事件和状态,由BLoC自主管理Timer生命周期,页面仅需发送启动/停止事件。
示例代码(BLoC部分):
// 扩展事件 abstract class ApiEvent {} class FetchData extends ApiEvent {} class StartTimer extends ApiEvent {} class StopTimer extends ApiEvent {} class _UpdateTimer extends ApiEvent {} // 内部事件,由Timer触发 // 扩展状态 abstract class ApiState {} class ApiLoading extends ApiState {} class ApiLoaded extends ApiState {} class ApiLoadedWithTimer extends ApiState { final Duration elapsed; ApiLoadedWithTimer(this.elapsed); } class ApiBloc extends Bloc<ApiEvent, ApiState> { Timer? _timer; final Stopwatch _stopwatch = Stopwatch(); ApiBloc() : super(ApiLoading()) { on<FetchData>(_handleFetchData); on<StartTimer>(_handleStartTimer); on<StopTimer>(_handleStopTimer); on<_UpdateTimer>(_handleUpdateTimer); } Future<void> _handleFetchData(FetchData event, Emitter<ApiState> emit) async { // 模拟API请求 await Future.delayed(const Duration(seconds: 2)); emit(ApiLoaded()); } void _handleStartTimer(StartTimer event, Emitter<ApiState> emit) { _stopwatch.start(); _timer = Timer.periodic(const Duration(seconds: 1), (_) => add(_UpdateTimer())); } void _handleStopTimer(StopTimer event, Emitter<ApiState> emit) { _timer?.cancel(); _stopwatch.stop(); } void _handleUpdateTimer(_UpdateTimer event, Emitter<ApiState> emit) { if (state is ApiLoaded) { emit(ApiLoadedWithTimer(_stopwatch.elapsed)); } } @override Future<void> close() { _timer?.cancel(); _stopwatch.stop(); return super.close(); } }
页面调用示例:
class YourPage extends StatefulWidget { const YourPage({super.key}); @override State<YourPage> createState() => _YourPageState(); } class _YourPageState extends State<YourPage> { @override void dispose() { // 页面销毁时通知BLoC停止Timer context.read<ApiBloc>().add(StopTimer()); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: BlocListener<ApiBloc, ApiState>( listenWhen: (prev, curr) => prev is ApiLoading && curr is ApiLoaded, listener: (_, __) => context.read<ApiBloc>().add(StartTimer()), child: BlocBuilder<ApiBloc, ApiState>( builder: (_, state) { if (state is ApiLoadedWithTimer) { return Text('计时: ${state.elapsed.inSeconds}s'); } return const CircularProgressIndicator(); }, ), ), ); } }
方案3:用Stream替代Timer,结合StreamSubscription
在BLoC中创建周期性Stream,用StreamSubscription管理,更符合响应式范式,也能安全控制生命周期。
示例代码(BLoC核心部分):
class ApiBloc extends Bloc<ApiEvent, ApiState> { StreamSubscription? _timerSubscription; final Stopwatch _stopwatch = Stopwatch(); ApiBloc() : super(ApiLoading()) { on<StartTimer>(_handleStartTimer); on<StopTimer>(_handleStopTimer); } void _handleStartTimer(StartTimer event, Emitter<ApiState> emit) { _stopwatch.start(); _timerSubscription = Stream.periodic(const Duration(seconds: 1)) .listen((_) => add(_UpdateTimer())); } void _handleStopTimer(StopTimer event, Emitter<ApiState> emit) { _timerSubscription?.cancel(); _stopwatch.stop(); } @override Future<void> close() { _timerSubscription?.cancel(); _stopwatch.stop(); return super.close(); } // 其他事件/状态处理逻辑... }
关键注意事项
- 核心原则:确保Timer/StreamSubscription在页面或BLoC销毁时被取消,避免资源泄漏和无效context调用。
- 在StatefulWidget中使用context时,必须在回调里先检查
mounted状态,防止页面销毁后调用context.read。 - 无需为简单Timer单独创建BLoC,优先选择页面State管理或扩展现有BLoC,避免过度设计。
内容的提问来源于stack exchange,提问作者Myrmillion
相关产品推荐
相关产品推荐

