FlutterBleLib
FlutterBleLib copied to clipboard
startPeripheralScan runs only every ODD time
Hello, and thank you for this library.
I have a strange issue with startPeripheralScan
method.
First run - runs ok, return results as expected (my device is off, so i don't find him, its ok)
Second run - I turn ON my device and run same startPeripheralScan
, but nothing happens.
Third run - I try to run startPeripheralScan
again - and its worked
And for example if i do not turn my device ON, just for simulation - on every EVEN run, startPeripheralScan
not works.
My code example:
@override
Future<void> initClient({@required Function onError}) async {
Log.debug(tag, "Init devices bloc - _initClient");
try {
await _bleManager
.createClient(
restoreStateIdentifier: "restore-state-identifier",
restoreStateAction: (peripherals) async {
if (peripherals != null) {
for (var peripheral in peripherals) {
Log.info(tag, "\t${peripheral.toString()} ");
await peripheral.disconnectOrCancelConnection();
}
}
},
)
.catchError((e) => Log.error(tag, "Couldn't create BLE client: $e"));
await _checkPermissions().catchError(
(e) async {
Log.error(tag, e);
onError(PairingStates.errorNoPermission);
throw BtIssue();
},
);
await _waitForBluetoothPoweredOn(onError);
} on BtIsOff {
onError(PairingStates.errorBtIsOff);
throw BtIsOff();
} catch (e) {
Log.error(tag, 'ERROR CATCHED 3 ${e.toString()}');
throw BtIssue();
}
}
void scanAndPair() async {
Log.debug(tag, "Ble client created - Start scan for devices");
var deviceFound = false;
final deviceName = bleRepo.loadDeviceName();
await bleService.disconnect();
await bleService.stopScan();
await scanSubscription?.cancel();
final timer = Timer(defaultScanTimeout, () async {
if (!deviceFound) {
Log.error(tag, "No Device was found");
await scanSubscription.cancel();
await bleService.stopScan();
pairingState = PairingStates.errorNoDevicesFound;
}
});
Log.debug(
tag, "Ble client created - Start scan for devices - TIMER STARTED");
scanSubscription = bleService.scanForDevices().listen((scanResult) async {
Log.debug(tag, 'Try to find device with name $deviceName');
Log.info(tag,
'Found new device name:${scanResult?.advertisementData?.localName} RSSI:${scanResult?.rssi}');
if (scanResult?.advertisementData?.localName != null &&
scanResult.advertisementData.localName.trimRight() == deviceName) {
if (timer.isActive) {
timer.cancel();
}
await bleService.stopScan();
final bleDevice = BleDevice(scanResult);
deviceFound = true;
Log.info(tag,
'The new device ${scanResult.advertisementData.localName} ${scanResult.peripheral.identifier}');
bleRepo.saveDeviceId(bleDevice.id);
device = bleDevice;
await scanSubscription.cancel();
connectToDevice(bleDevice: bleDevice, timeout: defaultConnectTimeout);
}
});
}
Stream<ScanResult> scanForDevices() {
return _bleManager.startPeripheralScan();
}
So for every EVEN run app is stuck at Ble client created - Start scan for devices - TIMER STARTED
message
Same bug on iOS, I got a XPC connection invalid every "odd" scan
Same bug. First time, scan is working. I select a device, i go back to list screen and then PeripheralScan is not working. No problem on Android, only with IOS.
@psycura This normally happens if you have called createClient multiple times without destroying it in between. see issues #526 & #532
I understand, but i do not destroy client between the scans. Maybe its because i run same sequence before each scan:
Future<void> initClient({required Function onError}) async {
Log.debug(tag, "Init devices bloc - _initClient");
try {
await _bleManager
.createClient(
restoreStateIdentifier: "restore-state-identifier",
restoreStateAction: (peripherals) async {
if (peripherals != null) {
for (final peripheral in peripherals) {
Log.info(tag, "\t${peripheral.toString()} ");
await peripheral.disconnectOrCancelConnection();
}
}
},
)
.catchError((e) => Log.error(tag, "Couldn't create BLE client: $e"));
await _checkPermissions().catchError(
(e) async {
Log.error(tag, e);
onError(PairingStates.errorNoPermission);
throw BtIssue();
},
);
await _waitForBluetoothPoweredOn(onError);
} on BtIsOff {
onError(PairingStates.errorBtIsOff);
throw BtIsOff();
} catch (e) {
Log.error(tag, 'ERROR CATCHED 3 ${e.toString()}');
throw BtIssue();
}
}
And .createClient
cause this issue.
If yes, the question is - do i have any option to know if bleManager.client
exists?
And also this do not explain why this issue happens only every EVEN scan
@psycura That's what is causing the problem, you are calling createClient multiple times without destroying in between. Either don't create the client multiple times or destroy it before calling create again.
@JamesMcIntosh thanks, i`ll try.
But 3 questions is still open:
- Why this happens only in IOS?
- Why this happens only every EVEN scan, and every ODD scan works, i do same init sequence every time, and do not destroy client between the scans
- Do i have option to check if client exists and initialized?
Thanks
@JamesMcIntosh The solution that you provided, dosn't solve the issue.
I tried 2 ways: destroy and create client before every scan, do not create client if it was created before. The result the same - every EVEN time scan doesn't work
@psycura What if you remove await peripheral.disconnectOrCancelConnection();
?
@JamesMcIntosh i`ll try, but this part of code is not executed, because at the initial i dont have any paired peripheral
@JamesMcIntosh As expected - removing of await peripheral.disconnectOrCancelConnection();
does not helps
@psycura You can also make sure that you're not getting any errors on the stream.
BleManager()
.startPeripheralScan()
.listen(
(ScanResult scanResult) { ... },
onError: (Object e) {
print("Error listening to scan results: ${e}");
},
onDone: () {
print("Finished listening to scan results");
},
@JamesMcIntosh
No error messages.
Straight received Finished listening to scan results
message (On EVEN scan)
@psycura If you add some breakpoints into FlutterBleLib/lib/src/bridge/scanning_mixin.dart
you might get some more insight. Such as if the _scanEvents field is null when stopDeviceScan is being called.
@JamesMcIntosh And how does it helps me?
@psycura Did it print "Finished listening..." on the first scan too?
I'm just getting you to go through the path I would follow if trying to find the source of the issue since I can't replicate it on my device.
If it's returning again straight away without scanning then you'd be asking if/why the stream is completed. In scanning_mixin you can see if it's reusing the _scanEvents stream and you can check what state it's in for each device scan.
From here it depends how deep you want to go looking... you can follow it into the iOS codebase and see what's happening in the scanningEvents channel and startDeviceScan implementation.
@JamesMcIntosh No at first scan there is no message Finished listening to scan results
This is strange because in timer that responsible for TIMOUT i call to stopPeripheralScan
final timer = Timer(defaultScanTimeout, () async {
if (!deviceFound) {
Log.error(
tag, "No Device was found total other devices found: $deviceCount");
await scanSubscription?.cancel();
await _bleManager.stopPeripheralScan();
}
});
So maybe reason in stopPeripheralScan
method, that not worked propertly
@psycura I assume you added the breakpoints I suggested in ScanningMixin
to make sure that ScanningMixin.stopDeviceScan()
is called when you expect it to be in relation to ScanningMixin.startDeviceScan()
as you may be doing something such as subscribing to the same event channel for both attempts then closing it which accessing it the second time.
@JamesMcIntosh, i very appreciate your help, however i can do only changes related to my code, and make sure that i use the library in the proper way. But if issue is happens at the library side, i expect, that it will be fixed by library`s developers. I understand, that we are speaking about open source project, and developer can decide to stop maintain his library. If this is the case - please notify as, and we can decide if and how we will continue to use this library.
Thanks
@psycura For now, I used flutter_blue for the scan on iOS and I continue to use FlutterBleLib by creating a peripheral with the createUnsafePeripheral method.
@dannyalbuquerque you mean use 2 libraries? One for scan, and second for pairing and interaction?
Why not to use only flutter_blue
?
I created a temporary solution for IOS - check if no devices was found at all, i simple make new scan.
@psycura Yes in case the application is already implemented with FlutterBleLib. It is also a temporary fix.
I created a temporary solution for IOS - check if no devices was found at all, i simple make new scan.
Good idea
@psycura The breakpoints in ScanningMixin are there to identify if your code is correctly interfacing with the iOS implementation. From what I have experienced the iOS part is a lot less forgiving for developer mistakes.
however i can do only changes related to my code, and make sure that i use the library in the proper way.
That's not true, you can manipulate the working copy of the dart code while running Android Studio and even change the iOS code while running the app from XCode.
@psycura @dannyalbuquerque I'd happily run/test an example app which demonstrates the failure if you want to make one up and publish it on your GitHub.
@JamesMcIntosh you can take the example project from the library.
The only thing that was changed is in pubspec.yaml
added to dependencies
flutter_ble_lib: ^2.3.2
First scan - display list of devices
Pull to refresh - flutter: 2021-03-18T12:25:15.063781 D DevicesBloc._startScan: Ble client created
appears in the log and nothing happens
Pull to refresh again - everything works as expected
I'm also trying to fix this problem on iOS now and tried what @JamesMcIntosh suggested.
In ScanningMixin every other time ScanningMixin.stopDeviceScan()
is called right after ScanningMixin.startDeviceScan()
.
_scanEvents is null every time we call ScanningMixin.stopDeviceScan()
It looks like something deeper (native side) doesn't work properly?
There is a simple workaround this which is to either comment out the _scanSubscription.cancel()
or swap it's order with the stopPeripheralScan call in the refresh method.
Future<void> refresh() async {
await _bleManager.stopPeripheralScan();
_scanSubscription.cancel();
bleDevices.clear();
_visibleDevicesController.add(bleDevices.sublist(0));
await _checkPermissions()
.then((_) => _startScan())
.catchError((e) => Fimber.d("Couldn't refresh", ex: e));
}
The source of the problem is due to an extra "close stream" (null) event being queued up on the "Scanning" EventChannel, this event is delivered when you start listening again to the EventChannels broadcast stream in ScanningMixin.startDeviceScan
.
streamController.addStream(_scanEvents, cancelOnError: true)
Does anyone have a good idea why it's happening only on iOS, looking at both the java and iOS implementations I would have expected it to occur in Java too?
To see it add a breakpoint inside the EventChannel.receiveBroadcastStream() implementation - platform_channel.dart line 528
Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) {
final MethodChannel methodChannel = MethodChannel(name, codec);
late StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen: () async {
binaryMessenger.setMessageHandler(name, (ByteData? reply) async {
if (reply == null) { // ADD BREAKPOINT HERE
controller.close();
} else {
try {
@kamil-chmiel @psycura @dannyalbuquerque @mikolak Looks like I have found the bottom of the rabbit hole.
Android succeeds because has protection in the EventChannel#L240 to stop double submission of "end of stream".
By the look of it iOS does not have the same sort of protections https://github.com/flutter/engine/blob/master/shell/platform/darwin/common/framework/Headers/FlutterChannels.h https://github.com/flutter/engine/blob/master/shell/platform/darwin/common/framework/Source/FlutterChannels.mm
The solution may be to track in ScanningStreamHandler
whether FlutterEndOfEventStream
has been sent to the FlutterEventSink
. I've added this to PR #583
This issue probably warrants raising a ticket in the Flutter code base to question the different behaviour.
Tremendous work, @JamesMcIntosh!
@JamesMcIntosh thanks for help )
Thanks to all of you for your investigation, extremely appreciated, it will help me a lot in my iOS app ! Especially @JamesMcIntosh .
Thankyou @JamesMcIntosh @psycura calling stopPeripheralScan before startPeripherScan worked for me.