Drawback
I am growing a Flutter app that makes use of the Polar BLE SDK for coronary heart charge 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.
Setting
- Flutter 3.35.2+
- iOS 17.x
- Polar BLE SDK: 5.11.0+ (works tremendous with 5.10.0)
- Machine: iPhone (bodily system)
- Take a look at {hardware}: Polar/Wahoo TICKR coronary heart charge displays
Minimal Reproducible Instance
Flutter Code (principal.dart)
import 'bundle:flutter/materials.dart';
import 'bundle:flutter/providers.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: Middle(
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(outcome: @escaping FlutterResult) {
guard let api = api else {
return outcome(FlutterError(code: "polarApiNil", message: nil, particulars: nil))
}
autoConnectDisposable?.dispose()
autoConnectDisposable = api.startAutoConnectToDevice(-80, service: CBUUID(string: "180D"), polarDeviceType: nil)
.subscribe { e in
swap e {
case .accomplished:
outcome(nil)
case .error(_):
outcome(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, outcome: @escaping FlutterResult) in
if name.technique == "autoConnectToDevice" {
self.hrService.autoConnectToDevice(outcome: outcome)
}
})
GeneratedPluginRegistrant.register(with: self)
return tremendous.software(software, didFinishLaunchingWithOptions: launchOptions)
}
}
The Impasse
When operating the app and urgent “Join”, the app freezes fully. The principle thread stack hint appears 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
- Totally 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 situation.
Query
How can I work round this thread synchronization situation 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 strategy to:
- Configure the Polar SDK to keep away from this impasse state of affairs?
- Initialize the SDK in a method that stops the impasse?
- Use an alternate connection method that bypasses the problematic code path?