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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:25:01