Kotlin Multiplatform BLE library for iOS, Android, macos, windows and javascript
Note: The 2.x API is considered legacy. New projects should use the 3.0 API directly. See the README for the current installation instructions.
If you have existing 2.x code and want the simplest possible upgrade, just update the version β no code changes required:
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon:3.4.0")
}
Your existing delegate-based code continues to work unchanged.
// Create instance
val blueFalcon = BlueFalcon(log = null, ApplicationContext())
// Register delegate
blueFalcon.delegates.add(object : BlueFalconDelegate {
override fun didDiscoverDevice(peripheral: BluetoothPeripheral, advertisementData: Map<AdvertisementDataRetrievalKeys, Any>) {
println("Found device: ${peripheral.name}")
}
override fun didConnect(peripheral: BluetoothPeripheral) {
println("Connected to: ${peripheral.name}")
}
})
// Start scanning
blueFalcon.scan()
Blue Falcon 3.0 introduces a revolutionary plugin-based engine architecture that provides:
Good news: Most applications can upgrade to 3.0 with zero code changes thanks to the compatibility layer.
blue-falcon-core: Platform-agnostic APIblue-falcon-engine-{platform}: Platform-specific implementationsLoggingPlugin: Debug BLE operationsRetryPlugin: Automatic retry with exponential backoffCachingPlugin: Cache GATT services and characteristicsblue-falcon-legacy module| Feature | 2.x | 3.0 |
|---|---|---|
| Modular Architecture | Monolithic | β Modular engines |
| Plugin Support | β | β Built-in plugin system |
| Kotlin Coroutines | Limited | β Full support |
| StateFlow APIs | β | β Reactive state |
| Custom Engines | β | β Easy to create |
| Independent Releases | β | β Per-platform |
| Community Extensions | β | β Supported |
Blue Falcon 3.0 offers three migration paths. Choose based on your needs:
Best for: Applications that want 3.0 benefits without code changes.
Steps:
// Before (2.x)
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon:2.0.0")
}
// After (3.0 - Legacy API)
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-core:3.0.0")
implementation("dev.bluefalcon:blue-falcon-legacy:3.0.0")
}
// Platform-specific engines
androidMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-android:3.0.0")
}
iosMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-ios:3.0.0")
}
// Your existing 2.x code - no changes needed!
val blueFalcon = BlueFalcon(log = null, ApplicationContext())
blueFalcon.delegates.add(myDelegate)
blueFalcon.scan()
Best for: Adding new features with 3.0 APIs while maintaining existing 2.x code.
Approach: Use both APIs side-by-side during transition.
// Existing 2.x code - keep using delegates
val legacyBlueFalcon = BlueFalcon(log = null, ApplicationContext())
legacyBlueFalcon.delegates.add(myDelegate)
// New code - use 3.0 Flow APIs
val modernBlueFalcon = dev.bluefalcon.core.BlueFalcon(
engine = AndroidBlueFalconEngine(context)
)
// Collect peripherals with Flow
modernBlueFalcon.peripherals.collect { peripherals ->
println("Found ${peripherals.size} devices")
}
Timeline: Migrate module-by-module or feature-by-feature at your own pace.
Best for: New projects or complete refactors wanting pure 3.0 APIs.
Steps:
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-core:3.0.0")
// Plugins (optional)
implementation("dev.bluefalcon:blue-falcon-plugin-logging:3.0.0")
implementation("dev.bluefalcon:blue-falcon-plugin-retry:3.0.0")
}
androidMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-android:3.0.0")
}
import dev.bluefalcon.core.*
import dev.bluefalcon.plugins.logging.*
import dev.bluefalcon.plugins.retry.*
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(LoggingPlugin) {
level = LogLevel.DEBUG
logConnections = true
logGattOperations = true
}
install(RetryPlugin) {
maxRetries = 3
initialDelay = 500.milliseconds
}
}
// Use coroutines and Flow
lifecycleScope.launch {
blueFalcon.scan()
blueFalcon.peripherals.collect { devices ->
println("Discovered: ${devices.size} devices")
}
}
If youβre using Strategy 1 (Zero-Change Migration) with blue-falcon-legacy, there are no breaking changes.
If migrating to pure 3.0 APIs:
dev.bluefalcon.*dev.bluefalcon.core.*, dev.bluefalcon.plugins.*// 2.x
BlueFalcon(log, context, autoDiscover)
// 3.0
BlueFalcon(engine = AndroidBlueFalconEngine(context))
// 2.x - Delegates
blueFalcon.delegates.add(object : BlueFalconDelegate {
override fun didDiscoverDevice(device: BluetoothPeripheral) { }
})
// 3.0 - StateFlow
blueFalcon.peripherals.collect { devices -> }
// 2.x - Synchronous
blueFalcon.scan()
// 3.0 - Suspend functions
suspend fun startScan() {
blueFalcon.scan()
}
// 2.x
val blueFalcon = BlueFalcon(
log = PrintLnLogger,
context = ApplicationContext(),
autoDiscoverAllServicesAndCharacteristics = true
)
// 3.0 Legacy (same as 2.x)
val blueFalcon = dev.bluefalcon.legacy.BlueFalcon(
log = PrintLnLogger,
context = ApplicationContext(),
autoDiscoverAllServicesAndCharacteristics = true
)
// 3.0 Modern
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(
context = context,
autoDiscoverServices = true
)
}
// 2.x
blueFalcon.scan()
blueFalcon.stopScanning()
// 3.0 Modern
lifecycleScope.launch {
blueFalcon.scan()
delay(10.seconds)
blueFalcon.stopScanning()
}
// 2.x - Delegate pattern
blueFalcon.delegates.add(object : BlueFalconDelegate {
override fun didDiscoverDevice(bluetoothPeripheral: BluetoothPeripheral) {
println("Found: ${bluetoothPeripheral.name}")
}
})
// 3.0 Modern - StateFlow
lifecycleScope.launch {
blueFalcon.peripherals.collect { peripherals ->
peripherals.forEach { device ->
println("Found: ${device.name}")
}
}
}
// 2.x
blueFalcon.connect(peripheral)
blueFalcon.delegates.add(object : BlueFalconDelegate {
override fun didConnect(bluetoothPeripheral: BluetoothPeripheral) {
println("Connected!")
}
})
// 3.0 Modern
lifecycleScope.launch {
try {
blueFalcon.connect(peripheral)
println("Connected!")
} catch (e: BluetoothException) {
println("Connection failed: ${e.message}")
}
}
// 2.x
blueFalcon.readCharacteristic(peripheral, characteristic)
blueFalcon.delegates.add(object : BlueFalconDelegate {
override fun didReadCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic
) {
val value = bluetoothCharacteristic.value
}
})
// 3.0 Modern
lifecycleScope.launch {
blueFalcon.readCharacteristic(peripheral, characteristic)
val value = characteristic.value
println("Read: ${value.decodeToString()}")
}
// 2.x
blueFalcon.writeCharacteristic(peripheral, characteristic, "Hello".encodeToByteArray())
// 3.0 Modern (supports String or ByteArray)
lifecycleScope.launch {
blueFalcon.writeCharacteristic(peripheral, characteristic, "Hello")
// or
blueFalcon.writeCharacteristic(peripheral, characteristic, byteArrayOf(0x01, 0x02))
}
// 2.x
class DeviceScanner(context: ApplicationContext) : BlueFalconDelegate {
private val blueFalcon = BlueFalcon(null, context)
private val devices = mutableListOf<BluetoothPeripheral>()
init {
blueFalcon.delegates.add(this)
}
fun startScanning() {
blueFalcon.scan()
}
override fun didDiscoverDevice(bluetoothPeripheral: BluetoothPeripheral) {
devices.add(bluetoothPeripheral)
}
}
// 3.0 Modern
class DeviceScanner(context: Context) {
private val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(LoggingPlugin) {
level = LogLevel.DEBUG
}
}
val devices: StateFlow<Set<BluetoothPeripheral>> = blueFalcon.peripherals
suspend fun startScanning() {
blueFalcon.scan()
}
}
// Usage
lifecycleScope.launch {
scanner.startScanning()
scanner.devices.collect { peripherals ->
updateUI(peripherals)
}
}
// 2.x
class DeviceManager(context: ApplicationContext) : BlueFalconDelegate {
private val blueFalcon = BlueFalcon(null, context)
private var onConnected: (() -> Unit)? = null
init {
blueFalcon.delegates.add(this)
}
fun connect(device: BluetoothPeripheral, callback: () -> Unit) {
onConnected = callback
blueFalcon.connect(device)
}
override fun didConnect(bluetoothPeripheral: BluetoothPeripheral) {
onConnected?.invoke()
}
}
// 3.0 Modern
class DeviceManager(context: Context) {
private val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(RetryPlugin) {
maxRetries = 3
}
}
suspend fun connectAndDiscover(device: BluetoothPeripheral): List<BluetoothService> {
blueFalcon.connect(device)
blueFalcon.discoverServices(device)
return device.services
}
}
// Usage
lifecycleScope.launch {
try {
val services = manager.connectAndDiscover(peripheral)
println("Found ${services.size} services")
} catch (e: BluetoothException) {
println("Error: ${e.message}")
}
}
// 2.x
class ConnectionMonitor : BlueFalconDelegate {
override fun didUpdateState(state: BluetoothPeripheralState) {
when (state) {
BluetoothPeripheralState.Connected -> println("Connected")
BluetoothPeripheralState.Disconnected -> println("Disconnected")
else -> {}
}
}
}
// 3.0 Modern
class ConnectionMonitor(private val blueFalcon: BlueFalcon) {
fun monitorState(peripheral: BluetoothPeripheral) = flow {
while (true) {
emit(blueFalcon.connectionState(peripheral))
delay(1.seconds)
}
}
}
// Usage
lifecycleScope.launch {
monitor.monitorState(peripheral).collect { state ->
when (state) {
BluetoothPeripheralState.Connected -> println("Connected")
BluetoothPeripheralState.Disconnected -> println("Disconnected")
else -> {}
}
}
}
Dependencies:
androidMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-android:3.0.0")
}
Permissions: No changes needed - same permissions as 2.x:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Engine Creation:
// Requires Android Context
val engine = AndroidBlueFalconEngine(context = applicationContext)
Dependencies:
iosMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-ios:3.0.0")
}
Engine Creation:
// No context needed on iOS
val engine = IosBlueFalconEngine()
Info.plist: Same requirements as 2.x:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth to connect to devices</string>
Dependencies:
macosMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-macos:3.0.0")
}
Dependencies:
jsMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-js:3.0.0")
}
Browser Support: Chrome, Edge, Opera (same as 2.x)
Dependencies:
windowsMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-windows:3.0.0")
}
Requirements: Windows 10 1803+ (same as 2.x)
Dependencies:
rpiMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-rpi:3.0.0")
}
One of the biggest advantages of 3.0 is the plugin system. Add powerful features with just a few lines:
Debug all BLE operations:
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(LoggingPlugin) {
level = LogLevel.DEBUG
logDiscovery = true
logConnections = true
logGattOperations = true
logger = PrintLnLogger
}
}
// Output:
// [BlueFalcon] [DEBUG] Starting scan with 0 filters
// [BlueFalcon] [INFO] Connected to peripheral: ABC123
// [BlueFalcon] [DEBUG] Reading characteristic 1234-5678
Automatic retry with exponential backoff:
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(RetryPlugin) {
maxRetries = 3
initialDelay = 500.milliseconds
maxDelay = 5.seconds
backoffMultiplier = 2.0
retryOn = { error -> error is BluetoothException }
}
}
// Automatically retries failed operations!
lifecycleScope.launch {
try {
blueFalcon.connect(peripheral) // Retries up to 3 times on failure
} catch (e: Exception) {
println("Failed after 3 retries")
}
}
Cache GATT services to reduce discovery time:
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(CachingPlugin) {
cacheSize = 10
ttl = 5.minutes
}
}
// First discovery - hits the device
blueFalcon.discoverServices(peripheral) // ~1-2 seconds
// Subsequent discoveries - cached!
blueFalcon.discoverServices(peripheral) // Instant!
Combine plugins for powerful functionality:
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(LoggingPlugin) {
level = LogLevel.INFO
}
install(RetryPlugin) {
maxRetries = 3
}
install(CachingPlugin) {
cacheSize = 10
}
}
A: No! Use the blue-falcon-legacy module for zero-change migration. Your 2.x code works as-is.
A: Yes! The legacy and modern APIs can coexist. Great for gradual migration.
A: Create a custom engine by implementing BlueFalconEngine. See PLUGIN_DEVELOPMENT_GUIDE.md.
A: No, plugins are optional. Core functionality works without any plugins.
A: Minimal impact. Plugins use interceptor pattern with negligible overhead.
A: Absolutely! See PLUGIN_DEVELOPMENT_GUIDE.md for details.
A: With blue-falcon-legacy, it continues working exactly as before. The legacy module wraps the new engine architecture.
A: No! Only include engines for platforms you target. Gradle resolves the correct one automatically in multiplatform projects.
A: Yes! 3.0 has been extensively tested and is production-ready. The legacy API ensures backward compatibility.
A: Check the examples/ directory for complete working samples:
examples/Android-Example - Android app using 3.0examples/KotlinMP-Example - Multiplatform exampleexamples/Plugin-Example - Plugin usage demonstrationA: Open an issue on GitHub with:
build.gradle.ktsWelcome to Blue Falcon 3.0! π