diff --git a/packages/flutter_reactive_ble/lib/src/device_connector.dart b/packages/flutter_reactive_ble/lib/src/device_connector.dart index 88e2a4fd..82abedd2 100644 --- a/packages/flutter_reactive_ble/lib/src/device_connector.dart +++ b/packages/flutter_reactive_ble/lib/src/device_connector.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:flutter_reactive_ble/src/device_scanner.dart'; import 'package:flutter_reactive_ble/src/rx_ext/repeater.dart'; @@ -110,49 +112,77 @@ class DeviceConnectorImpl implements DeviceConnector { Duration? connectionTimeout, List withServices, Duration prescanDuration, - ) { + ) async* { if (_deviceIsDiscoveredRecently( deviceId: id, cacheValidity: _scanRegistryCacheValidityPeriod)) { - return connect( + yield* connect( id: id, servicesWithCharacteristicsToDiscover: servicesWithCharacteristicsToDiscover, connectionTimeout: connectionTimeout, ); - } else { - final scanSubscription = _deviceScanner - .scanForDevices( - withServices: withServices, scanMode: ScanMode.lowLatency) - .listen((DiscoveredDevice scanData) {}, onError: (Object _) {}); + return; + } + + // get a sync function context so we can cancel the stream on a simple + // timer + Stream scanWithTimeout() { + final controller = StreamController(); + + final stream = _deviceScanner.scanForDevices( + withServices: withServices, scanMode: ScanMode.lowLatency); + + final scanSubscription = + stream.listen(controller.add, onError: controller.addError); Future.delayed(prescanDuration).then((_) { scanSubscription.cancel(); + controller.close(); }); - return _deviceScanner.currentScan!.future - .then((_) => true) - .catchError((Object _) => false) - .asStream() - .asyncExpand( - (succeeded) { - if (succeeded) { - return _connectIfRecentlyDiscovered( - id, servicesWithCharacteristicsToDiscover, connectionTimeout); - } else { - // When the scan fails 99% of the times it is due to violation of the scan threshold: - // https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983 - // - // Previously we used "autoconnect" but that gives slow connection times (up to 2 min) on a lot of devices. - return Future.delayed(_delayAfterScanFailure) - .asStream() - .asyncExpand((_) => _connectIfRecentlyDiscovered( - id, - servicesWithCharacteristicsToDiscover, - connectionTimeout, - )); - } - }, + return controller.stream; + } + + var didScanDevice = false; + try { + await for (final device in scanWithTimeout()) { + if (device.id == id) { + didScanDevice = true; + break; + } + } + // ignore: avoid_catches_without_on_clauses + } catch (_) { + // When the scan fails 99% of the times it is due to violation of the scan threshold: + // https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983 + // + // Previously we used "autoconnect" but that gives slow connection times (up to 2 min) on a lot of devices. + await Future.delayed(_delayAfterScanFailure); + + yield* _connectIfRecentlyDiscovered( + id, + servicesWithCharacteristicsToDiscover, + connectionTimeout, ); + return; } + + if (didScanDevice) { + yield* connect( + id: id, + servicesWithCharacteristicsToDiscover: + servicesWithCharacteristicsToDiscover, + connectionTimeout: connectionTimeout, + ); + return; + } + + yield ConnectionStateUpdate( + deviceId: id, + connectionState: DeviceConnectionState.disconnected, + failure: const GenericFailure( + code: ConnectionError.failedToConnect, + message: "Device is not advertising"), + ); } Stream _awaitCurrentScanAndConnect( diff --git a/packages/flutter_reactive_ble/test/device_connector_test.dart b/packages/flutter_reactive_ble/test/device_connector_test.dart index 0d193ef1..52f4cf53 100644 --- a/packages/flutter_reactive_ble/test/device_connector_test.dart +++ b/packages/flutter_reactive_ble/test/device_connector_test.dart @@ -93,15 +93,6 @@ void main() { const deviceId = '123'; final uuidDeviceToScan = Uuid.parse('FEFF'); final uuidCurrentScan = Uuid.parse('FEFE'); - final discoveredDevice = DiscoveredDevice( - id: 'deviceId', - manufacturerData: Uint8List.fromList([0]), - serviceUuids: const [], - name: 'test', - rssi: -39, - connectable: Connectable.unknown, - serviceData: const {}, - ); setUp(() { _connectionStateUpdateStream = Stream.fromIterable([ @@ -235,15 +226,23 @@ void main() { }); group('And device is not discovered in a previous scan', () { - setUp(() { - when(_scanner.scanForDevices( - withServices: anyNamed('withServices'), - scanMode: anyNamed('scanMode'), - )).thenAnswer((_) => Stream.fromIterable([discoveredDevice])); - }); - group('And device is not found after scanning', () { setUp(() { + when(_scanner.scanForDevices( + withServices: anyNamed('withServices'), + scanMode: anyNamed('scanMode'), + )).thenAnswer((_) => Stream.fromIterable([ + DiscoveredDevice( + id: 'deviceId', + manufacturerData: Uint8List.fromList([0]), + serviceUuids: const [], + name: 'test', + rssi: -39, + connectable: Connectable.unknown, + serviceData: const {}, + ) + ])); + when(_registry.deviceIsDiscoveredRecently( deviceId: deviceId, cacheValidity: anyNamed('cacheValidity'))) @@ -272,6 +271,21 @@ void main() { }); group('And device found after scanning', () { setUp(() { + when(_scanner.scanForDevices( + withServices: anyNamed('withServices'), + scanMode: anyNamed('scanMode'), + )).thenAnswer((_) => Stream.fromIterable([ + DiscoveredDevice( + id: deviceId, + manufacturerData: Uint8List.fromList([0]), + serviceUuids: const [], + name: 'test', + rssi: -39, + connectable: Connectable.unknown, + serviceData: const {}, + ) + ])); + final responses = [false, true, true, true]; when(_registry.deviceIsDiscoveredRecently( deviceId: deviceId,