I am new in kotlin multiplatform. Can you please h...
# multiplatform
s
I am new in kotlin multiplatform. Can you please help me for implementing Bluetooth/Ble in KMM
đź§µ 3
l
This is not really multiplatform-related. There is an iphone-related channel that is probably better suited for your question.
s
#C3PQML5NU I have to implement BLE functionality by using kotlin multiplatform. I know android Ble implementation but this will not work as android lib not supported by shared part. So, I need help for it. I have done some coding for it but callback methods are not getting call
Copy code
class BluetoothServiceIOSNew : BluetoothService {

    // Central and Peripheral Managers
    private val centralManager: CBCentralManager = CBCentralManager()
    private var peripheralManager: CBPeripheralManager? = null
    private var connectedPeripheral: CBPeripheral? = null
    private var peripheral: CBPeripheral? = null


    // Define properties for the callbacks
    private var connectSuccessCallback: (() -> Unit)? = null
    private var connectFailureCallback: ((Throwable) -> Unit)? = null
    private var disconnectCallback: (() -> Unit)? = null
    private var disconnectFailureCallback: ((Throwable) -> Unit)? = null


    init {
        println("Kotlin::: init execution")
        centralManager.delegate = object : NSObject(), CBCentralManagerDelegateProtocol {

            // Called when Bluetooth state changes (powered on/off)
            //This method is called whenever the Bluetooth state of the CBCentralManager changes (e.g., powered on, powered off, unsupported, etc.).
            override fun centralManagerDidUpdateState(manager: CBCentralManager) {
                println("Kotlin::: centralManagerDidUpdateState")
                when (manager.state) {
                    CBCentralManagerStatePoweredOn -> {
                        println("Kotlin::: Bluetooth is powered on.")
                    }
                    CBCentralManagerStatePoweredOff -> {
                        println("Kotlin::: Bluetooth is powered off.")
                    }
                    else -> {
                        println("Kotlin::: Bluetooth is unavailable or turned off.")
                    }
                }
            }

            // Called when a peripheral is discovered during scanning
            //This method is called when a peripheral device is discovered during a scan. You can use it to filter peripherals and initiate a connection.
            override fun centralManager(central: CBCentralManager, didDiscoverPeripheral: CBPeripheral, advertisementData: Map<Any?, *>, RSSI: NSNumber) {
                println("Kotlin:: Discovered peripheral: ${didDiscoverPeripheral.name}")
//                if (didDiscoverPeripheral.name == "TargetDeviceName") {
                    centralManager.connectPeripheral(didDiscoverPeripheral, options = null)
                    connectedPeripheral = didDiscoverPeripheral
                    println("Kotlin:: Connecting to peripheral: ${didDiscoverPeripheral.name}")
//                }
            }

            // Called when a peripheral has been successfully connected
            //This method is called when a peripheral is successfully connected. You can use this to trigger actions upon a successful connection.
            override fun centralManager(manager: CBCentralManager, didConnectPeripheral: CBPeripheral) {
                println("Kotlin:: Successfully connected to peripheral: ${didConnectPeripheral.name}")
                connectedPeripheral = didConnectPeripheral
                connectSuccessCallback?.invoke()  // Invoke the success callback
            }

            // Called when a connection to a peripheral fails
            //This method is called when a connection attempt to a peripheral fails. It provides an error so you can handle the failure.
            @ObjCSignatureOverride
            override fun centralManager(manager: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?) {
                println("Kotlin:: Failed to connect to peripheral: ${didFailToConnectPeripheral.name}")
                connectFailureCallback?.invoke((error ?: Exception("Unknown connection failure")) as Throwable) // Invoke failure callback
            }

            // Called when a peripheral is disconnected
            //This method is called when a peripheral is disconnected, either intentionally or due to an error.
            @ObjCSignatureOverride
            override fun centralManager(manager: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?) {
                println("Kotlin:: Disconnected from peripheral: ${didDisconnectPeripheral.name}")
                connectedPeripheral = null
                if (error != null) {
                    disconnectFailureCallback?.invoke(error.toThrowable())  // If there's an error on disconnect, invoke failure callback
                } else {
                    disconnectCallback?.invoke()  // Otherwise, invoke success callback
                }
            }
        }
    }

    fun NSError.toThrowable(): Throwable {
        return Throwable("Kotlin::: NSError code: $code, description: ${localizedDescription}")
    }

    override fun isBluetoothPoweredOn(): Boolean {
        return when (centralManager.state) {
            CBCentralManagerStatePoweredOn -> true
            else -> false
        }
    }

    override suspend fun startAdvertising(name: String, serviceUUID: String) {
        println("Kotlin::: Starting advertising with name: $name and service UUID: $serviceUUID")
        if (peripheralManager == null) {
            println("Kotlin::: Null execution")
            // Initialize the peripheral manager with the correct delegate
            peripheralManager = CBPeripheralManager()

            // Set the delegate after initialization
            peripheralManager?.delegate = BluetoothPeripheralManagerDelegateNew(
                onAdvertisingStarted = {
                    println("Kotlin::: Advertising started successfully!")
                },
                onAdvertisingFailed = { error ->
                    println("Kotlin::: Advertising failed with error: ${error.localizedDescription}")
                }
            )
        }

        // Wait until the peripheral manager is powered on
        while (peripheralManager?.state != CBPeripheralManagerStatePoweredOn) {
            delay(500) // Check every 500ms
        }

        val advertisementData: Map<Any?, Any?> = mapOf(
            CBAdvertisementDataLocalNameKey to name,
            CBAdvertisementDataServiceUUIDsKey to listOf(CBUUID.UUIDWithString(serviceUUID))
        )

//        peripheralManager?.startAdvertising(advertisementData)
        peripheralManager?.startAdvertising(null)
        println("Kotlin::: Started advertising with name: $name and service UUID: $serviceUUID")
    }

    override suspend fun stopAdvertising() {
        peripheralManager?.stopAdvertising()
        println("Kotlin:: Stopped advertising.")
    }

    override suspend fun startDiscovery(serviceUUID: String) {
        println("Kotlin:: Starting scan for peripherals offering service UUID: $serviceUUID")
        withContext(Dispatchers.Main) {
            val serviceUUIDs = listOf(CBUUID.UUIDWithString(serviceUUID))
            centralManager.scanForPeripheralsWithServices(serviceUUIDs, options = null)
            println("Kotlin:: Started scanning for peripherals offering service UUID: $serviceUUID")
        }
    }

    override suspend fun stopDiscovery() {
        withContext(Dispatchers.Main) {
            centralManager.stopScan()
            println("Kotlin:: Stopped scanning for peripherals.")
        }
    }

    @Throws(Exception::class)
    override suspend fun connect(deviceId: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
        println("Kotlin:: connect-1")

        if (deviceId.isNullOrEmpty()) {
            onFailure(IllegalArgumentException("Device ID cannot be null or empty"))
            return
        }

        println("Kotlin:: connect-2")
        val nsUUID = NSUUID(deviceId)
        println("Kotlin:: connect Created NSUUID: $nsUUID")

        // Retrieve peripheral by UUID
        val peripheralToConnect = centralManager.retrievePeripheralsWithIdentifiers(listOf(nsUUID))
        if (!peripheralToConnect.isNullOrEmpty()) {
            peripheral = peripheralToConnect.firstOrNull() as? CBPeripheral
            println("Kotlin:: connect-5 :: $peripheral")
            if (peripheral != null) {
                centralManager.connectPeripheral(peripheral!!, options = null)
                println("Kotlin:: Attempting to connect to peripheral: $deviceId")

                // Store the callback functions for success and failure
                connectSuccessCallback = onSuccess
                connectFailureCallback = onFailure
            } else {
                onFailure(IllegalArgumentException("Peripheral not valid"))
            }
        } else {
            onFailure(IllegalArgumentException("Peripheral not found"))
        }
    }

    // Implement disconnect with success and failure callbacks
    override suspend fun disconnect(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) {
        println("Kotlin:: disconnect-1")

        if (connectedPeripheral != null) {
            centralManager.cancelPeripheralConnection(connectedPeripheral!!)
            println("Kotlin:: Disconnecting from peripheral: ${connectedPeripheral?.name}")
            // Store the callback for disconnection success
            disconnectCallback = onSuccess
            disconnectFailureCallback = onFailure
        } else {
            println("Kotlin:: No peripheral is connected.")
            onFailure(IllegalStateException("No peripheral is connected"))
        }
    }

}

// Bluetooth Peripheral Manager Delegate
class BluetoothPeripheralManagerDelegateNew(
    private val onAdvertisingStarted: () -> Unit,
    private val onAdvertisingFailed: (NSError) -> Unit
) : NSObject(), CBPeripheralManagerDelegateProtocol {

    // This callback will be triggered when advertising starts
    override fun peripheralManagerDidStartAdvertising(peripheral: CBPeripheralManager, error: NSError?) {
        if (error == null) {
            println("Kotlin::: oAdvertising-Started")
            onAdvertisingStarted()
        } else {
            println("Kotlin::: oAdvertising-Failed")
            onAdvertisingFailed(error)
        }
    }

    // This callback is triggered when the peripheral manager's state changes (e.g., powered on/off)
    override fun peripheralManagerDidUpdateState(peripheral: CBPeripheralManager) {
        when (peripheral.state) {
            CBPeripheralManagerStatePoweredOn -> {
                println("Kotlin::: Peripheral Manager powered on.")
            }
            else -> {
                println("Kotlin::: Peripheral Manager not ready or powered off.")
            }
        }
    }
}
Copy code
interface BluetoothService {
    fun isBluetoothPoweredOn(): Boolean
    suspend fun startAdvertising(name: String, serviceUUID: String)
    suspend fun stopAdvertising()
    suspend fun startDiscovery(serviceUUID: String)
    suspend fun stopDiscovery()

 /*   @Throws(Exception::class)
    suspend fun connect(deviceId: String)
    suspend fun disconnect()*/

    @Throws(Exception::class)
    suspend fun connect(deviceId: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit)
    suspend fun disconnect(onSuccess: () -> Unit, onFailure: (Throwable) -> Unit)
}
a
I have used Kable successfully before. It’s a multiplatform library for BLE, maybe it fits your needs
s
Ok. So, Kable lib classes/method can be called from swift classes?
a
I don’t know. Maybe that’s a general question, can you call Kotlin code from Swift? My instinct tells me you don’t want to call Kable code directly from Swift. Instead, write Kotlin code (in common) to encapsulate BLE logic you want (e.g. in something like a viewmodel or whatever your architecture is), then “consume” that on Swift side if needed
s
But I have to create a Ble lib which can be use in android and ios both and in ios swift class will be used
a
Kable is a ready-made solution to be used from common Kotlin code (no support for advertising though, you would have to add that). I hope someone else can help with your requirements and implementation.
s
If anyone has any idea about it, please help me