diff --git a/ios/RNBluetoothClassic.swift b/ios/RNBluetoothClassic.swift index 87754c28..b1bc21f5 100644 --- a/ios/RNBluetoothClassic.swift +++ b/ios/RNBluetoothClassic.swift @@ -30,7 +30,7 @@ import CoreBluetooth data is done in Javascript/client rather than in the module. */ @objc(RNBluetoothClassic) -class RNBluetoothClassic : NSObject, RCTBridgeModule { +class RNBluetoothClassic : NSObject, RCTBridgeModule, CBCentralManagerDelegate { static func moduleName() -> String! { return "RNBluetoothClassic" @@ -49,7 +49,7 @@ class RNBluetoothClassic : NSObject, RCTBridgeModule { * By using Lazy initialization on CBCentralManager it will prompt bluetooth permission * on first call of any bluetooth-related method. */ - private lazy var cbCentral: CBCentralManager = CBCentralManager() + private lazy var cbCentral: CBCentralManager = CBCentralManager(delegate: self, queue: nil) private let notificationCenter: NotificationCenter private let supportedProtocols: [String] @@ -57,6 +57,10 @@ class RNBluetoothClassic : NSObject, RCTBridgeModule { private var listeners: Dictionary private var connections: Dictionary + // Track CBCentralManager state + private var centralManagerState: CBManagerState = .unknown + private var stateUpdateCallbacks: [(CBManagerState) -> Void] = [] + /** * Initializes the RNBluetoothClassic module. At this point it's not quite as customizable as the * Java version, but I'm slowly working on figuring out how to incorporate the same logic in a @@ -180,10 +184,28 @@ class RNBluetoothClassic : NSObject, RCTBridgeModule { */ @objc func isBluetoothEnabled( - _ resolve: RCTPromiseResolveBlock, - rejecter reject: RCTPromiseRejectBlock + _ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock ) -> Void { - resolve(checkBluetoothAdapter()) + // If state is already determined (not unknown), return immediately + if centralManagerState != .unknown { + resolve(checkBluetoothAdapter()) + return + } + + // State is unknown, need to wait for state update + // Trigger lazy initialization if needed + _ = cbCentral + + // Add callback to be executed when state updates + stateUpdateCallbacks.append { [weak self] _ in + if let self = self { + resolve(self.checkBluetoothAdapter()) + } else { + // If self is deallocated, resolve with false as a safe default + resolve(false) + } + } } /** @@ -193,9 +215,9 @@ class RNBluetoothClassic : NSObject, RCTBridgeModule { var enabled = false if #available(iOS 10.0, *) { - enabled = (cbCentral.state == CBManagerState.poweredOn) + enabled = (centralManagerState == CBManagerState.poweredOn) } else { - enabled = (cbCentral.state.rawValue == CBCentralManagerState.poweredOn.rawValue) + enabled = (centralManagerState.rawValue == CBCentralManagerState.poweredOn.rawValue) } return enabled @@ -648,6 +670,30 @@ class RNBluetoothClassic : NSObject, RCTBridgeModule { } } +// MARK: CBCentralManagerDelegate implementation +/** + * Extension implementing the CBCentralManagerDelegate + * + * Required to properly track the CBCentralManager state which is asynchronously + * initialized. This fixes the issue where isBluetoothEnabled would return false + * on the first call because the state hadn't been initialized yet. + */ +extension RNBluetoothClassic { + + func centralManagerDidUpdateState(_ central: CBCentralManager) { + // Update our cached state + centralManagerState = central.state + + // Execute any pending callbacks waiting for state update + let callbacks = stateUpdateCallbacks + stateUpdateCallbacks.removeAll() + + for callback in callbacks { + callback(central.state) + } + } +} + // MARK: BluetoothReceivedDelegate implementation /** * Extension implementing the DataReceivedDelegate