Kotlin Multiplatform BLE library for iOS, Android, macos, windows and javascript
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! π