Flutter Cubit实战:如何有效拆分BLE设备的事件流?
问题:拆分Cubit模块,实现BLE跑步机连接与运动状态管理分离
背景
我是经验丰富的C#开发者,正在学习Flutter的Cubit功能。目前通过Flutter Blue获取BLE跑步机的事件流,能将设备发送的特征值字节映射为WorkoutStatus类实例。但当前的TreadmillControlService既要处理蓝牙连接管理,又要处理运动状态更新,我认为应该拆分为跑步机设备管理和运动状态管理两个独立模块。
现有方案与困惑
目前有两种思路:
- 方案一:用
TreadmillWorkoutUnion联合类耦合两者,虽然可行但不够优雅 - 方案二:像C#里那样,把事件流拆成两个独立流,分别对应
TreadmillState和WorkoutStatus的Cubit,但不知道具体怎么实现,想请教问题出在哪
相关代码
WorkoutStatus类
class WorkoutStatus { double speedInKmh; double distanceInKm; int timeInSeconds; int indicatedCalories; int steps; WorkoutStatus({ required this.speedInKmh, required this.distanceInKm, required this.timeInSeconds, required this.indicatedCalories, required this.steps, }); // 字节转实体的映射代码已省略 }
耦合的TreadmillControlService Cubit
class TreadmillControlService extends Cubit<TreadmillWorkoutUnion> { BluetoothDevice? _device; BluetoothCharacteristic? _control; BluetoothCharacteristic? _workoutStatus; // 用双下划线确保通过setter访问 double __requestedSpeed = 0; WorkoutStatus? _status; static const double minSpeed = 1; static const double maxSpeed = 6; TreadmillControlService(super.initialState) { FlutterBluePlus.setLogLevel(LogLevel.warning, color: false); } // 跑步机连接后调用此方法 Future<void> _setupServices() async { await _device!.discoverServices(); var fitnessMachine = _device!.servicesList.firstWhere((s) => s.uuid == Guid("1826")); _control = fitnessMachine.characteristics.firstWhere((c) => c.uuid == Guid("2ad9")); _workoutStatus = fitnessMachine.characteristics.firstWhere((c) => c.uuid == Guid("2acd")); _workoutStatus!.onValueReceived.listen(_processStatusUpdate); _workoutStatus!.setNotifyValue(true); } void _processStatusUpdate(List<int> value) { _status = WorkoutStatus.fromBytes(value); // 这里需要发送状态更新 } // 以下方法实现已省略,因为无关 Future<void> connect() async { } Future<void> _wakeup() async { } Future<void> start() async { } Future<void> stop() async { } Future<void> _setSpeed(double speed) async { } void pause() { } Future<void> speedUp() async { } Future<void> speedDown() async { } set _requestedSpeed(double value) { } double get _requestedSpeed => __requestedSpeed; }
TreadmillWorkoutUnion联合类
class TreadmillWorkoutUnion { TreadmillState treadmillState; WorkoutStatus workoutStatus; TreadmillWorkoutUnion(this.treadmillState, this.workoutStatus); }
当前的状态处理逻辑
void _processStatusUpdate(List<int> value) { final workoutStatus = WorkoutStatus.fromBytes(value); // 可以考虑用工厂方法创建 final treadmillState = TreadmillState( speedState: workoutStatus.speedInKmh == _requestedSpeed ? SpeedState.steady : workoutStatus.speedInKmh < _requestedSpeed ? SpeedState.increasing : SpeedState.decreasing, connectionState: _device!.isConnected ? ConnectionState.connected : ConnectionState.disconnected, requestedSpeed: _requestedSpeed, currentSpeed: workoutStatus.speedInKmh); emit(TreadmillWorkoutUnion(treadmillState, workoutStatus)); }
解决方案:拆分两个独立Cubit+依赖注入共享BLE事件流
核心思路是把原来的单一Cubit拆成两个职责单一的模块:
TreadmillConnectionCubit:专门负责蓝牙连接、设备发现、服务初始化,同时暴露BLE的状态更新流WorkoutStatusCubit:专门处理运动状态的解析、更新,依赖前者提供的事件流
步骤1:定义各自的State
给两个Cubit分别定义独立的状态类:
// 跑步机连接状态枚举 enum ConnectionState { disconnected, connecting, connected, error } enum SpeedState { steady, increasing, decreasing } // 跑步机连接状态实体 class TreadmillConnectionState { final ConnectionState connectionState; final BluetoothCharacteristic? workoutStatusChar; final double requestedSpeed; final double currentSpeed; final SpeedState speedState; TreadmillConnectionState({ required this.connectionState, this.workoutStatusChar, this.requestedSpeed = 0, this.currentSpeed = 0, this.speedState = SpeedState.steady, }); // 拷贝方法,用于生成新状态 TreadmillConnectionState copyWith({ ConnectionState? connectionState, BluetoothCharacteristic? workoutStatusChar, double? requestedSpeed, double? currentSpeed, SpeedState? speedState, }) { return TreadmillConnectionState( connectionState: connectionState ?? this.connectionState, workoutStatusChar: workoutStatusChar ?? this.workoutStatusChar, requestedSpeed: requestedSpeed ?? this.requestedSpeed, currentSpeed: currentSpeed ?? this.currentSpeed, speedState: speedState ?? this.speedState, ); } } // 运动状态包装类 class WorkoutStatusState { final WorkoutStatus? status; WorkoutStatusState(this.status); }
步骤2:实现TreadmillConnectionCubit
这个Cubit只处理蓝牙相关逻辑,同时对外暴露运动状态特征值的流:
class TreadmillConnectionCubit extends Cubit<TreadmillConnectionState> { BluetoothDevice? _device; BluetoothCharacteristic? _controlChar; final _workoutStatusStreamController = StreamController<List<int>>.broadcast(); // 对外暴露运动状态特征值的流 Stream<List<int>> get workoutStatusStream => _workoutStatusStreamController.stream; static const double minSpeed = 1; static const double maxSpeed = 6; double _requestedSpeed = 0; TreadmillConnectionCubit() : super(TreadmillConnectionState(connectionState: ConnectionState.disconnected)) { FlutterBluePlus.setLogLevel(LogLevel.warning, color: false); } Future<void> connect() async { emit(state.copyWith(connectionState: ConnectionState.connecting)); try { // 这里实现蓝牙设备搜索、连接逻辑,赋值给_device await _setupServices(); emit(state.copyWith(connectionState: ConnectionState.connected)); } catch (e) { emit(state.copyWith(connectionState: ConnectionState.error)); } } Future<void> _setupServices() async { await _device!.discoverServices(); var fitnessMachine = _device!.servicesList.firstWhere((s) => s.uuid == Guid("1826")); _controlChar = fitnessMachine.characteristics.firstWhere((c) => c.uuid == Guid("2ad9")); final workoutStatusChar = fitnessMachine.characteristics.firstWhere((c) => c.uuid == Guid("2acd")); // 监听特征值变化,转发到控制器 workoutStatusChar.onValueReceived.listen((value) { _workoutStatusStreamController.add(value); // 更新连接状态里的速度相关字段 final status = WorkoutStatus.fromBytes(value); final speedState = status.speedInKmh == _requestedSpeed ? SpeedState.steady : status.speedInKmh < _requestedSpeed ? SpeedState.increasing : SpeedState.decreasing; emit(state.copyWith( currentSpeed: status.speedInKmh, speedState: speedState, )); }); await workoutStatusChar.setNotifyValue(true); emit(state.copyWith(workoutStatusChar: workoutStatusChar)); } // 蓝牙控制方法:只负责发送BLE指令 Future<void> speedUp() async { final newSpeed = _requestedSpeed + 1; if (newSpeed <= maxSpeed) { _requestedSpeed = newSpeed; await _setSpeed(newSpeed); emit(state.copyWith(requestedSpeed: newSpeed)); } } Future<void> speedDown() async { final newSpeed = _requestedSpeed - 1; if (newSpeed >= minSpeed) { _requestedSpeed = newSpeed; await _setSpeed(newSpeed); emit(state.copyWith(requestedSpeed: newSpeed)); } } Future<void> _setSpeed(double speed) async { // 实现发送速度指令到BLE设备的逻辑 } @override Future<void> close() { _workoutStatusStreamController.close(); return super.close(); } }
步骤3:实现WorkoutStatusCubit
这个Cubit依赖TreadmillConnectionCubit的流,只处理运动状态解析:
class WorkoutStatusCubit extends Cubit<WorkoutStatusState> { late final StreamSubscription<List<int>> _subscription; final TreadmillConnectionCubit _connectionCubit; WorkoutStatusCubit(this._connectionCubit) : super(WorkoutStatusState(null)) { // 订阅连接Cubit的运动状态流 _subscription = _connectionCubit.workoutStatusStream.listen((value) { final status = WorkoutStatus.fromBytes(value); emit(WorkoutStatusState(status)); }); } @override Future<void> close() { _subscription.cancel(); return super.close(); } }
步骤4:在UI层使用两个Cubit
通过MultiBlocProvider在Widget树中提供两个Cubit:
MultiBlocProvider( providers: [ BlocProvider(create: (context) => TreadmillConnectionCubit()), BlocProvider(create: (context) => WorkoutStatusCubit(context.read<TreadmillConnectionCubit>())), ], child: YourTreadmillScreen(), )
然后在UI中分别监听两个Cubit的状态:
// 监听连接状态 BlocBuilder<TreadmillConnectionCubit, TreadmillConnectionState>( builder: (context, state) { return Text('连接状态:${state.connectionState.name}'); }, ), // 监听运动状态 BlocBuilder<WorkoutStatusCubit, WorkoutStatusState>( builder: (context, state) { final status = state.status; if (status == null) return Text('无运动数据'); return Column( children: [ Text('速度:${status.speedInKmh} km/h'), Text('距离:${status.distanceInKm} km'), // 其他运动数据展示 ], ); }, )
方案优势
- 单一职责:每个Cubit只做一件事,逻辑清晰,易维护
- 解耦:两者通过流通信,修改其中一个模块不会影响另一个
- 贴合C#习惯:和你熟悉的C#事件流拆分思路一致,降低学习成本
内容的提问来源于stack exchange,提问作者ScottishTapWater
相关产品推荐
相关产品推荐

