HomeiOS DevelopmentPolar BLE SDK impasse in Flutter app when calling startAutoConnectToDevice (iOS)

Polar BLE SDK impasse in Flutter app when calling startAutoConnectToDevice (iOS)


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

  1. Completely different dispatch queues – Initializing Polar API with background queues, principal queue, customized serial queues
  2. Async dispatch – Wrapping the startAutoConnectToDevice name in numerous async dispatches
  3. 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:

  1. Configure the Polar SDK to keep away from this impasse state of affairs?
  2. Initialize the SDK in a method that forestalls the impasse?
  3. Use another connection method that bypasses the problematic code path?

Associated

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments