flutter_blue
flutter_blue copied to clipboard
FlutterBlue.instance.scanResults shows stale results
Hi,
i hope it won't be redundant. I'm currently facing problem with FlutterBlue.instance.scanResults stream. I'm trying to display/process List of ScanResult and it shows stale results which are no longer available or in range. That's crucial for my application cause I try to make an app which opens doors and there will be potentially dozens of BLE enabled devices.
Edit:
I've come up with ugly looking quick fix but it's less than ideal:
Observable<ScanResult>(FlutterBlue.instance.scan( scanMode: ScanMode(2))).bufferTime(Duration(milliseconds: 500)).asyncMap((value) => value.toSet().toList()..sort((a,b)=> a.device.id.id.compareTo(b.device.id.id)))
Are you saving the results in a Map? For example Map<DeviceIdentifier, ScanResult> scanResults = Map();. Because then you can just clear the list before every scan scanResults.clear(); And the stale (old) results are gone.
I'm not saving results in a map. My intend is to scan all the time without need to refresh. User is suppose to walk through the building and depending upon rssi threshold app should connect to BLE device and communicate with it.
Your results should be saved somewhere. How are you using the results if you don't have them stored somewhere?
The Flutter Blue code actually does seem to clear the _scanResults list before every scan:
_scanResults.add(<ScanResult>[]);
You could also make your scan recursive. Set a timeout and after the scan is completed you call scan again.
Scanning can be done asynchronous. So you could also keep scanning endlessly and then every (i.e.) 3 seconds just clear a local scan result list. Or maybe better for your situation. Save the results in a list (i.e. called 'current list') and then after 3 seconds copy it to another list (i.e. called 'check list'). Then compare both lists. And (after 3 seconds) if a result from check list is not present in current list you remove it from check list. The only thing is that you need to use check list so you are 3 seconds behind the actual scan results.
Maybe you can share your full scan code.
I think I'm gonna put that stream of scan results Observable<ScanResult>(FlutterBlue.instance.scan( withServices: [Guid(myUUID)], scanMode: ScanMode(2))) .bufferTime(Duration(milliseconds: 500)).distinct((p,n)=> p.isEmpty&&n.isEmpty) .asyncMap((value) => value.toSet().toList() ..sort((a, b) => a.device.id.id.compareTo(b.device.id.id))) which every 500ms yields me fresh List of buffered ScanResults into Bloc and if any rssi will be under threshold I'll attempt to connect to it.
The Flutter Blue code actually clear the _scanResults list just on scan start:
Stream<ScanResult> scan({
ScanMode scanMode = ScanMode.lowLatency,
List<Guid> withServices = const [],
List<Guid> withDevices = const [],
Duration timeout,
}) async* {
var settings = protos.ScanSettings.create()
..androidScanMode = scanMode.value
..serviceUuids.addAll(withServices.map((g) => g.toString()).toList());
if (_isScanning.value == true) {
throw Exception('Another scan is already in progress.');
}
....
// Clear scan results list
_scanResults.add(<ScanResult>[]);
try {
await _channel.invokeMethod('startScan', settings.writeToBuffer());
} catch (e) {
print('Error starting scan.');
_stopScanPill.add(null);
_isScanning.add(false);
throw e;
}
....
}
The list is no longer cleaned during the scan. The items are just added or updated, so it contains all devices that are no longer available:
yield* Observable(FlutterBlue.instance._methodStream
.where((m) => m.method == "ScanResult")
.map((m) => m.arguments))
.takeUntil(Observable.merge(killStreams))
.doOnDone(stopScan)
.map((buffer) => new protos.ScanResult.fromBuffer(buffer))
.map((p) {
final result = new ScanResult.fromProto(p);
final list = _scanResults.value;
int index = list.indexOf(result);
if (index != -1) {
list[index] = result;
} else {
list.add(result);
}
_scanResults.add(list);
return result;
});
Could be useful a public method on FlutterBlue class that we can use to reset the scanResults Stream. Something like that:
void resetScanResult(){
// Clear scan results list
_scanResults.add(<ScanResult>[]);
}
I'm a bit hesitant to expose list manipulation methods on the API, as it could get a bit crowded.
This might be better handled in the end application.
I'm open to checking out PR's for this.
How about a method which sets a timeout for every device after which it would be removed unless it would be still in range. I try to avoid clearing list completely and rebuilding it up from scratch.
yes, this actually becomes an issue when we listen to scanResults asynchronously. Say the start and stop scan methods are invoked only on widget creation and dispose, listening to scanResults stream sends stale data (i.e) sends devices that are just discovered from beginning of scan even though they are not in range anymore.
Temporary decision
await FlutterBlue.instance.startScan(timeout: Duration(milliseconds: 1));
await Future.delayed(Duration(milliseconds: 3));
FlutterBlue.instance.scanResults.listen((snapshot) {
// do smth
});
await FlutterBlue.instance.startScan(timeout: Duration(seconds: 4));
any updates? @daniels20000