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

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()),
            ],
          ),
        ),
      ),
    );
  }
}

问题原因

  1. 计时器未被正确取消:每次调用increment都会创建新的Timer.periodic实例,旧计时器不会自动停止,即使调用reset重置了计数器,旧计时器仍会继续触发递增逻辑,导致计数器再次增加。
  2. 重复触发计时器启动:Connectivity的流可能重复发送相同的InternetConnected状态(如网络波动场景),导致BlocListener多次调用increment,创建多个并行运行的计时器。
  3. 状态重复发送:若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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 08:05:27