From 3b1aa4d4264615166895aabc2a323484ab0c239b Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Tue, 3 Mar 2026 22:55:14 -0600 Subject: [PATCH] Fix mobile BLE scan stream completion and cleanup --- lib/services/bluetooth/mobile_bluetooth.dart | 29 +++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/services/bluetooth/mobile_bluetooth.dart b/lib/services/bluetooth/mobile_bluetooth.dart index 6131f83..d279477 100644 --- a/lib/services/bluetooth/mobile_bluetooth.dart +++ b/lib/services/bluetooth/mobile_bluetooth.dart @@ -46,6 +46,7 @@ class MobileBluetoothService implements BluetoothService { StreamSubscription? _connectionStateSubscription; StreamSubscription? _notificationSubscription; StreamSubscription? _scanSubscription; + StreamController? _scanController; // Store scanned device info for use in connect() // This preserves the device name from scan results @@ -183,10 +184,15 @@ class MobileBluetoothService implements BluetoothService { @override Stream scanForDevices({Duration? timeout}) async* { final controller = StreamController(); + _scanController = controller; _updateStatus(ConnectionStatus.scanning); try { + // Ensure any previous scan stream is closed before starting a new one + await _scanSubscription?.cancel(); + _scanSubscription = null; + // Start scanning with filter for MeshCore service UUID await fbp.FlutterBluePlus.startScan( withServices: [fbp.Guid(BleUuids.serviceUuid)], @@ -208,14 +214,30 @@ class MobileBluetoothService implements BluetoothService { ); // Store device info for use in connect() - preserves name from scan _scannedDevices[device.id] = device; - controller.add(device); + if (!controller.isClosed) { + controller.add(device); + } } }); + // Complete stream when scan naturally stops (timeout or platform stop) + unawaited(() async { + await fbp.FlutterBluePlus.isScanning.where((isScanning) => !isScanning).first; + if (!controller.isClosed) { + await controller.close(); + } + }()); + yield* controller.stream; } finally { await _scanSubscription?.cancel(); _scanSubscription = null; + if (!controller.isClosed) { + await controller.close(); + } + if (identical(_scanController, controller)) { + _scanController = null; + } } } @@ -224,6 +246,11 @@ class MobileBluetoothService implements BluetoothService { await fbp.FlutterBluePlus.stopScan(); await _scanSubscription?.cancel(); _scanSubscription = null; + final scanController = _scanController; + if (scanController != null && !scanController.isClosed) { + await scanController.close(); + } + _scanController = null; // NOTE: Do NOT fire 'disconnected' here. Stopping a scan is not a disconnection. // The status will be updated by connect() when a connection starts. // Firing 'disconnected' here causes a race condition where the queued event