Flutter:使用Hooks监听类变量更新以刷新Widget树
问题分析
你的核心问题是useState(BC.instance!.scan)仅在_ShowScanningStatus首次构建时读取scan的初始值,后续BC类中scan变量的更新无法触发Hook的状态更新——因为useState没有监听外部对象属性变化的能力。要实现UI随扫描状态同步更新,需要让BC类的状态变化能主动通知Widget树。
解决方案:用Stream广播扫描状态
结合你现有代码中使用Stream的习惯,我们可以给BC类添加一个专门的Stream来广播扫描状态,然后在Widget中监听这个Stream实现UI更新。
步骤1:修改BC类,添加扫描状态Stream
class BC { static BC? instance; BCState state = BCState.offline; List<DiscoveredDevice> scanResults = []; StreamSubscription<DiscoveredDevice>? scan; // 新增:管理扫描状态的广播StreamController final _isScanningController = StreamController<bool>.broadcast(); // 对外暴露的扫描状态Stream Stream<bool> get isScanningStream => _isScanningController.stream; Stream<BleStatus> bleStatusStream = Stream<BleStatus>.periodic( const Duration(seconds: 1), (computationCount) { return flutterReactiveBle.status; }, ); late Stream<List<DiscoveredDevice>> scanResultsStream; BC() { BC.instance = this; scanResultsStream = Stream<List<DiscoveredDevice>>.periodic( const Duration(seconds: 1), (computationCount) { return scanResults; }, ); } // 新增:销毁时关闭StreamController,避免内存泄漏 void dispose() { _isScanningController.close(); scan?.cancel(); } Future<void> checkPermissions() async { // 原有权限检查代码不变 if (!await Permission.bluetooth.isGranted) { await Permission.bluetooth.request(); } if (!await Permission.bluetoothConnect.isGranted) { await Permission.bluetoothConnect.request(); } if (!await Permission.bluetoothScan.isGranted) { await Permission.bluetoothScan.request(); } if (!await Permission.bluetoothAdvertise.isGranted) { await Permission.bluetoothAdvertise.request(); } } void searchDevice() async { await checkPermissions(); if (scan != null) return; // 扫描开始:发送扫描中状态 _isScanningController.add(true); scan = flutterReactiveBle.scanForDevices( withServices: [], ).listen((device) { if (device.name.isEmpty) return; if (scanResults.every((element) => element.id != device.id)) { debugPrint("New device detected: ${device.id} ${device.name}"); List<DiscoveredDevice> newScanResults = []; for (var scan in scanResults) { newScanResults.add(scan); } scanResults = newScanResults..add(device); } }); Future.delayed(const Duration(seconds: 5)).then((value) { scan?.cancel(); scan = null; // 扫描结束:发送已停止状态 _isScanningController.add(false); debugPrint("stop scan"); }); } }
步骤2:修改_ShowScanningStatus Widget,监听扫描状态Stream
class _ShowScanningStatus extends HookWidget { const _ShowScanningStatus(); @override Widget build(BuildContext context) { // 使用useStream监听扫描状态Stream,初始值设为false(未扫描) final scanState = useStream(BC.instance!.isScanningStream, initialData: false); return Container( color: Colors.black45, height: 70, width: double.maxFinite, child: scanState.data == false ? Row( children: const [ Padding( padding: EdgeInsets.fromLTRB(16, 0, 16, 0), child: Icon( Icons.check, size: 40, ), ), Text('Scan completed.'), ], ) : Row( children: const [ Padding( padding: EdgeInsets.fromLTRB(16, 0, 16, 0), child: CircularProgressIndicator(), ), Text('Scanning for bluetooth devices...') ], ), ); } }
额外注意事项
- 在应用退出或BC实例不再使用时,记得调用
BC.instance?.dispose()关闭StreamController,防止内存泄漏。 - 如果你更倾向于使用状态管理库(如Provider、Riverpod),也可以将BC改为ChangeNotifier,在
scan变量变化时调用notifyListeners(),然后用useListenable监听状态变化,实现类似效果。
内容的提问来源于stack exchange,提问作者Colin Schneider
相关产品推荐
相关产品推荐

