Flutter Bloc自动重复发送旧状态:断网后计时器无法保持重置状态
问题描述
想要基于Internet Cubit实现一个计时器,当InternetDisconnected状态触发时重置计时器,但重置后计时器会再次开始递增,同时存在旧状态重复发送、计时器无法停留在0的问题。
现有代码
Home Cubit(计时器Cubit)
class HomeCubit extends Cubit<HomeState> { HomeCubit() : super(HomeState.initial()); void increment() { Timer.periodic(const Duration(seconds: 1), (timer) { emit(state.copyWith(counter: state.counter + 1)); }); } void reset() { emit( state.copyWith(counter: 0), ); } }
Home State(计时器状态)
class HomeState extends Equatable { final int counter; const HomeState({ required this.counter, }); factory HomeState.initial() { return const HomeState(counter: 0); } HomeState copyWith({ int? counter, }) { return HomeState( counter: counter ?? this.counter, ); } @override List<Object?> get props => [counter]; }
Internet Cubit
class InternetCubit extends Cubit<InternetState> { final Connectivity connectivity; late StreamSubscription internetStreamSubscription; InternetCubit({required this.connectivity}) : super(InternetInitial()) { monitorInternet(); } void monitorInternet() { internetStreamSubscription = connectivity.onConnectivityChanged.listen((connectivityResult) { if (connectivityResult == ConnectivityResult.wifi) { emit(const InternetConnected(connectionType: ConnectionType.wifi)); } else if (connectivityResult == ConnectivityResult.mobile) { emit(const InternetConnected(connectionType: ConnectionType.mobileData)); } else if (connectivityResult == ConnectivityResult.none) { emit(InternetDisconnected()); } }); } @override Future<void> close() { internetStreamSubscription.cancel(); return super.close(); } }
Internet State
enum ConnectionType { wifi, mobileData } abstract class InternetState extends Equatable { const InternetState(); @override List<Object> get props => []; } class InternetInitial extends InternetState {} class InternetConnected extends InternetState { final ConnectionType connectionType; const InternetConnected({required this.connectionType}); @override List<Object> get props => [connectionType]; } class InternetDisconnected extends InternetState {}
首页视图
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return BlocListener<InternetCubit, InternetState>( listener: (context, state) { if (state is InternetConnected) { context.read<HomeCubit>().increment(); } else if (state is InternetDisconnected) { context.read<HomeCubit>().reset(); } }, child: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('You have been connected to the Internet for the past'), Text(context.watch<HomeCubit>().state.counter.toString()), ], ), ), ), ); } }
问题原因
- 计时器未被正确取消:每次调用
increment都会创建新的Timer.periodic实例,旧计时器不会自动停止,即使调用reset重置了计数器,旧计时器仍会继续触发递增逻辑,导致计数器再次增加。 - 重复触发计时器启动:
Connectivity的流可能重复发送相同的InternetConnected状态(如网络波动场景),导致BlocListener多次调用increment,创建多个并行运行的计时器。 - 状态重复发送:若
InternetCubit重复发送相同状态(如网络状态未变化但流仍触发),会导致BlocListener重复执行逻辑,进一步加剧计时器异常。
解决方案
1. 优化HomeCubit,管理计时器生命周期
在HomeCubit中保存计时器实例,确保重置或重新启动时取消旧计时器:
class HomeCubit extends Cubit<HomeState> { Timer? _timer; HomeCubit() : super(HomeState.initial()); void startTimer() { // 先取消旧计时器,避免多计时器并行 _timer?.cancel(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { emit(state.copyWith(counter: state.counter + 1)); }); } void resetTimer() { // 取消计时器 _timer?.cancel(); _timer = null; // 重置计数器 emit(state.copyWith(counter: 0)); } @override Future<void> close() { _timer?.cancel(); return super.close(); } }
2. 优化BlocListener逻辑,避免重复触发
修改首页视图的BlocListener,仅在网络状态从断开变为连接时启动计时器,从连接变为断开时重置计时器:
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return BlocListener<InternetCubit, InternetState>( listenWhen: (previous, current) { // 仅在从断开/初始状态变为连接时触发启动 return (previous is InternetDisconnected && current is InternetConnected) || (previous is InternetInitial && current is InternetConnected); }, listener: (context, state) { context.read<HomeCubit>().startTimer(); }, child: BlocListener<InternetCubit, InternetState>( listenWhen: (previous, current) { // 仅在从连接变为断开时触发重置 return previous is InternetConnected && current is InternetDisconnected; }, listener: (context, state) { context.read<HomeCubit>().resetTimer(); }, child: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('你已联网时长(秒):'), Text(context.watch<HomeCubit>().state.counter.toString()), ], ), ), ), ), ); } }
3. 优化InternetCubit,避免重复发送相同状态
修改InternetCubit的monitorInternet方法,仅在状态变化时发送新状态:
void monitorInternet() { internetStreamSubscription = connectivity.onConnectivityChanged.listen((connectivityResult) { InternetState newState; if (connectivityResult == ConnectivityResult.wifi) { newState = const InternetConnected(connectionType: ConnectionType.wifi); } else if (connectivityResult == ConnectivityResult.mobile) { newState = const InternetConnected(connectionType: ConnectionType.mobileData); } else { newState = InternetDisconnected(); } // 仅当新状态与当前状态不同时才发送 if (newState != state) { emit(newState); } }); }
修改后,计时器会在联网时启动,断网时停止并重置为0,不会出现重置后又递增的问题,同时避免了重复触发逻辑。
内容的提问来源于stack exchange,提问作者Tanvir Ibn Mizan
相关产品推荐
相关产品推荐

