startScan static method

Future<void> startScan({
  1. List<Guid> withServices = const [],
  2. Duration? timeout,
  3. Duration? removeIfGone,
  4. bool oneByOne = false,
  5. bool androidUsesFineLocation = false,
})

Start a scan, and return a stream of results

  • timeout calls stopScan after a specified duration
  • removeIfGone if true, remove devices after they've stopped advertising for X duration
  • oneByOne if true, we will stream every advertistment one by one, including duplicates. If false, we deduplicate the advertisements, and return a list of devices.
  • androidUsesFineLocation request ACCESS_FINE_LOCATION permission at runtime

Implementation

static Future<void> startScan({
  List<Guid> withServices = const [],
  Duration? timeout,
  Duration? removeIfGone,
  bool oneByOne = false,
  bool androidUsesFineLocation = false,
}) async {
  await _initialize();

  // stop existing scan
  if (_isScanning.latestValue == true) {
    await stopScan();
  }

  // push to stream
  _isScanning.add(true);

  // Start timer *after* stream is being listened to, to make sure the
  // timeout does not fire before _buffer is set
  if (timeout != null) {
    _scanTimeout = Timer(timeout, stopScan);
  }

  /// remove connection by OS.
  /// The reason why we add this logic is
  /// to avoid uncontrollable devices and to make consistency.

  /// add WinBle scanning
  WinBle.startScanning();

  // check every 250ms for gone devices?
  late Stream<BleDevice?> outputStream;
  if (removeIfGone != null) {
    outputStream = _mergeStreams(
      [WinBle.scanStream, Stream.periodic(Duration(milliseconds: 250))],
    );
  } else {
    outputStream = WinBle.scanStream;
  }

  final output = <ScanResult>[];

  // listen & push to `scanResults` stream
  _scanSubscription = outputStream.listen(
    (BleDevice? winBleDevice) {
      if (winBleDevice == null) {
        // if null, this is just a periodic update for removing old results
        output.removeWhere((elm) =>
            DateTime.now().difference(elm.timeStamp) > removeIfGone!);

        // push to stream
        _scanResultsList.add(List.from(output));
      } else {
        final remoteId = DeviceIdentifier(winBleDevice.address.toUpperCase());
        final existedName = output
            .where((sr) => sr.device.remoteId == remoteId)
            .firstOrNull
            ?.device
            .platformName;
        final deviceName = winBleDevice.name.isNotEmpty
            ? winBleDevice.name
            : existedName ?? '';

        final device = BluetoothDeviceWindows(
          platformName: deviceName,
          remoteId: remoteId,
          rssi: int.tryParse(winBleDevice.rssi) ?? -100,
        );
        final sr = ScanResult(
          device: device,
          advertisementData: AdvertisementData(
            advName: deviceName,
            txPowerLevel: winBleDevice.adStructures
                ?.where((e) => e.type == 10)
                .singleOrNull
                ?.data
                .firstOrNull,
            //TODO: Should verify
            connectable: !winBleDevice.advType.contains('Non'),
            manufacturerData: {
              if (winBleDevice.manufacturerData.length >= 2)
                winBleDevice.manufacturerData[0]:
                    winBleDevice.manufacturerData.sublist(2),
            },
            //TODO: implementation missing
            serviceData: {},
            serviceUuids: winBleDevice.serviceUuids
                .map((e) =>
                    Guid((e as String).replaceAll(RegExp(r'[{}]'), '')))
                .toList(),
            appearance: null,
          ),
          rssi: int.tryParse(winBleDevice.rssi) ?? -100,
          timeStamp: DateTime.now(),
        );

        // add result to output
        if (oneByOne) {
          output
            ..clear()
            ..add(sr);
        } else {
          output.addOrUpdate(sr);
        }

        // push to stream
        _scanResultsList.add(List.from(output));
      }
    },
  );
}