Drawback
I am creating a Flutter app that makes use of the Polar BLE SDK for coronary heart fee monitoring on iOS. The app works completely with Polar BLE SDK model 5.10.0, however ranging from model 5.11.0 onwards (together with newest 6.5.0), the app freezes when calling startAutoConnectToDevice
from Flutter’s platform channel.
The difficulty seems to be associated to a threading change within the SDK the place Set
was modified to AtomicType
for thread security.
Atmosphere
- Flutter 3.35.2+
- iOS 17.x
- Polar BLE SDK: 5.11.0+ (works tremendous with 5.10.0)
- Machine: iPhone (bodily system)
- Check {hardware}: Polar/Wahoo TICKR coronary heart fee displays
Minimal Reproducible Instance
Flutter Code (principal.dart)
import 'bundle:flutter/materials.dart';
import 'bundle:flutter/companies.dart';
void principal() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({tremendous.key});
@override
Widget construct(BuildContext context) {
return MaterialApp(
house: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
static const platform = MethodChannel('com.instance.app/polar');
@override
Widget construct(BuildContext context) {
return Scaffold(
physique: Heart(
baby: TextButton(
onPressed: () {
platform.invokeMethod('autoConnectToDevice');
},
baby: Textual content('Join'),
),
),
);
}
}
iOS Code (HrService.swift)
import PolarBleSdk
import RxSwift
import CoreBluetooth
class HrService: PolarBleApiObserver {
var api: PolarBleApi? = nil
personal var autoConnectDisposable: Disposable?
func initializeApi() {
api = PolarBleApiDefaultImpl.polarImplementation(DispatchQueue.principal, options: [])
api!.observer = self
}
func deviceConnecting(_ identifier: PolarDeviceInfo) {
NSLog("deviceConnecting")
}
func deviceConnected(_ identifier: PolarDeviceInfo) {
NSLog("deviceConnected")
}
func deviceDisconnected(_ identifier: PolarDeviceInfo, pairingError: Bool) {
NSLog("deviceDisconnected")
}
func autoConnectToDevice(end result: @escaping FlutterResult) {
guard let api = api else {
return end result(FlutterError(code: "polarApiNil", message: nil, particulars: nil))
}
autoConnectDisposable?.dispose()
autoConnectDisposable = api.startAutoConnectToDevice(-80, service: CBUUID(string: "180D"), polarDeviceType: nil)
.subscribe { e in
change e {
case .accomplished:
end result(nil)
case .error(_):
end result(FlutterError())
@unknown default:
break
}
}
}
}
iOS AppDelegate.swift
@principal
@objc class AppDelegate: FlutterAppDelegate {
let POLAR_METHOD_CHANNEL = "com.instance.app/polar"
let hrService = HrService()
override func software(
_ software: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let polarMethodChannel = FlutterMethodChannel(title: POLAR_METHOD_CHANNEL,
binaryMessenger: controller.binaryMessenger)
hrService.initializeApi()
polarMethodChannel.setMethodCallHandler({ (name: FlutterMethodCall, end result: @escaping FlutterResult) in
if name.methodology == "autoConnectToDevice" {
self.hrService.autoConnectToDevice(end result: end result)
}
})
GeneratedPluginRegistrant.register(with: self)
return tremendous.software(software, didFinishLaunchingWithOptions: launchOptions)
}
}
The Impasse
When operating the app and urgent “Join”, the app freezes utterly. The principle thread stack hint seems like this:
Thread 1 Queue : com.apple.main-thread (serial)
#0 0x00000001e2126628 in __psynch_mutexwait ()
#1 0x00000001fcc6ac94 in _pthread_mutex_firstfit_lock_wait ()
#2 0x00000001fcc69cac in _pthread_mutex_firstfit_lock_slow ()
#3 0x0000000102ba25b0 in AtomicType.accessItem(_:) at .../AtomicType.swift:26
#4 0x000000010669fcbc in CBScanner.scanningNeeded() at .../CBScanner.swift:133
#5 0x0000000106686170 in CBDeviceListenerImpl.updateSessionState(_:state:)
#6 0x000000010668cd80 in CBDeviceListenerImpl.openSessionDirect(_:)
#7 0x000000010681f248 in closure #2 in PolarBleApiImpl.startAutoConnectToDevice(_:service:polarDeviceType:)
...
#29 0x0000000102ba25f4 in AtomicType.accessItem(_:) at .../AtomicType.swift:27
#30 0x0000000102c10e08 in closure #3 in CBDeviceListenerImpl.handleDeviceDiscovered(_:didDiscover:advertisementData:rssi:)
Root Trigger
The SDK change from model 5.10.0 to five.11.0 in CBScanner.swift
:
- var scanObservers = Set>()
+ var scanObservers = AtomicType(initialValue: Set>())
func addClient(_ scanner: RxObserver){
- scanObservers.insert(scanner)
+ scanObservers.accessItem { $0.insert(scanner) }
self.commandState(ScanAction.clientStartScan)
}
What I’ve Tried
- Completely different dispatch queues – Initializing Polar API with background queues, principal queue, customized serial queues
- Async dispatch – Wrapping the
startAutoConnectToDevice
name in numerous async dispatches - Delayed execution – Utilizing
DispatchQueue.principal.asyncAfter
with delays
None of those approaches resolved the impasse challenge.
Query
How can I work round this thread synchronization challenge in Polar BLE SDK 5.11.0+ when utilizing it from a Flutter app? The identical code works tremendous in native iOS apps, suggesting it is associated to how Flutter’s platform channels work together with the SDK’s threading mannequin.
Is there a technique to:
- Configure the Polar SDK to keep away from this impasse state of affairs?
- Initialize the SDK in a method that forestalls the impasse?
- Use another connection method that bypasses the problematic code path?