Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 53 additions & 7 deletions ios/RNBluetoothClassic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -49,14 +49,18 @@ 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]

private var listeners: Dictionary<String,Int>
private var connections: Dictionary<String,DeviceConnection>

// 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
Expand Down Expand Up @@ -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)
}
}
}

/**
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down