startScan method

  1. @override
Stream<BleDevice> startScan({
  1. List<ScanFilter>? filters,
  2. ScanSettings? settings,
})
override

Starts a scan for nearby Bluetooth Low Energy (BLE) devices and returns a stream of discovered devices.

Scanning for BLE devices is a crucial step in establishing a BLE connection. It allows the mobile app to discover nearby BLE devices and gather essential information like device name, MAC address, and more. This method starts the scanning operation on the platform side and listens for discovered devices.

The function takes optional filters and settings parameters that allow for more targeted device scanning. For example, you could specify a filter to only discover devices that are advertising a specific service. Similarly, settings allows you to adjust aspects like scan mode, report delay, and more.

The method uses a StreamController to handle the asynchronous nature of BLE scanning. Every time a device is discovered by the native platform, the 'bleDeviceScanned' method is invoked, and the device information is parsed and added to the stream.

Implementation

@override
Stream<BleDevice> startScan({
  List<ScanFilter>? filters,
  ScanSettings? settings,
}) {
  final StreamController<BleDevice> streamController = StreamController<BleDevice>.broadcast();

  // Listen to the platform side for scanned devices.
  channel.setMethodCallHandler((MethodCall call) async {
    switch (call.method) {
      case 'bleDeviceScanned':
        try {
          BleDevice device;
          // Different operating systems will send the arguments in different formats. So, normalize the arguments
          // as a Map<dynamic, dynamic> for use in the BleDevice.fromMap factory constructor.
          if (call.arguments is String) {
            final Map<dynamic, dynamic> argumentsParsed =
                json.decode(call.arguments as String) as Map<dynamic, dynamic>;
            device = BleDevice.fromMap(argumentsParsed);
          } else {
            device = BleDevice.fromMap(call.arguments as Map<dynamic, dynamic>);
          }

          // Apply additional filtering on Dart side. Filtering is also done on the native side, but this allows for
          // more complex filtering logic that may not be feasible on the native side.
          // If the device matches the provided filters, add it to the stream.
          if (filters?.deviceMatchesFilters(device) ?? true) {
            streamController.add(device);
          }
        } catch (e) {
          streamController.addError(
            FormatException('Failed to parse discovered device info: $e'),
          );
        }
      case 'error':
        streamController.addError(Exception(call.arguments));
    }
  });

  // Convert filters and settings into map representations if provided.
  final List<Map<String, dynamic>>? filtersMap = filters?.map((filter) => filter.toMap()).toList();
  final Map<String, dynamic>? settingsMap = settings?.toMap();

  // Begin the scan on the platform side, including the filters and settings in the method call if provided.
  channel.invokeMethod('startScan', {
    'filters': filtersMap,
    'settings': settingsMap,
  });

  return streamController.stream;
}