Blue Falcon Migration Guide
June 1, 2026 Β· View on GitHub
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.
Table of Contents
- Overview
- What's New in 3.0
- Why Migrate
- Migration Strategies
- Breaking Changes
- API Mapping: 2.x β 3.0
- Common Migration Patterns
- Platform-Specific Considerations
- Plugin Usage
- FAQ
Quick Reference
Installing with 2.x Compatibility
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.1")
}
Your existing delegate-based code continues to work unchanged.
Legacy API (2.x Style)
// 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()
Overview
Blue Falcon 3.0 introduces a revolutionary plugin-based engine architecture that provides:
- Modular design: Separate core API from platform implementations
- Plugin system: Extend functionality with logging, retry, caching, and custom plugins
- Improved performance: Modern Kotlin coroutines and Flow APIs
- Full backward compatibility: Existing 2.x code continues to work without changes
Good news: Most applications can upgrade to 3.0 with zero code changes thanks to the compatibility layer.
What's New in 3.0
π― Core Improvements
-
Plugin-Based Architecture: Separate core API from platform engines
blue-falcon-core: Platform-agnostic APIblue-falcon-engine-{platform}: Platform-specific implementations- Independent versioning and release cycles
-
Plugin System: Extend Blue Falcon with cross-cutting concerns
LoggingPlugin: Debug BLE operationsRetryPlugin: Automatic retry with exponential backoffCachingPlugin: Cache GATT services and characteristics- Custom plugins: Build your own!
-
Modern Kotlin APIs:
- Kotlin coroutines for async operations
- StateFlow for reactive state management
- Suspend functions for cleaner async code
-
Backward Compatibility Layer:
- Full 2.x API support via
blue-falcon-legacymodule - Delegate-based callbacks still supported
- Zero-change migration path
- Full 2.x API support via
Why Migrate
Benefits of Upgrading
| 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 |
When to Migrate
- β Now: If you want plugin support, better error handling, or modern APIs
- β Soon: If you need custom BLE functionality or platform extensions
- βΈοΈ Later: If your current 2.x code is stable and working perfectly
Migration Strategies
Blue Falcon 3.0 offers three migration paths. Choose based on your needs:
Strategy 1: Zero-Change Migration (Recommended for most apps)
Best for: Applications that want 3.0 benefits without code changes.
Steps:
- Update your dependencies:
// 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")
}
- That's it! Your 2.x code works as-is:
// Your existing 2.x code - no changes needed!
val blueFalcon = BlueFalcon(log = null, ApplicationContext())
blueFalcon.delegates.add(myDelegate)
blueFalcon.scan()
Strategy 2: Gradual Migration (Recommended for new features)
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.
Strategy 3: Full Migration (Best for new projects)
Best for: New projects or complete refactors wanting pure 3.0 APIs.
Steps:
- Update dependencies (core + engine only, no legacy):
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")
}
- Use modern 3.0 APIs with DSL configuration:
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")
}
}
Breaking Changes
β οΈ None for Legacy API Users
If you're using Strategy 1 (Zero-Change Migration) with blue-falcon-legacy, there are no breaking changes.
For Full 3.0 API Users (Strategy 3)
If migrating to pure 3.0 APIs:
-
Package names changed:
- Old:
dev.bluefalcon.* - New:
dev.bluefalcon.core.*,dev.bluefalcon.plugins.*
- Old:
-
Constructor changes:
// 2.x BlueFalcon(log, context, autoDiscover) // 3.0 BlueFalcon(engine = AndroidBlueFalconEngine(context)) -
Delegates β Flow:
// 2.x - Delegates blueFalcon.delegates.add(object : BlueFalconDelegate { override fun didDiscoverDevice(device: BluetoothPeripheral) { } }) // 3.0 - StateFlow blueFalcon.peripherals.collect { devices -> } -
Sync β Async:
// 2.x - Synchronous blueFalcon.scan() // 3.0 - Suspend functions suspend fun startScan() { blueFalcon.scan() }
API Mapping: 2.x β 3.0
Initialization
// 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
)
}
Scanning
// 2.x
blueFalcon.scan()
blueFalcon.stopScanning()
// 3.0 Modern
lifecycleScope.launch {
blueFalcon.scan()
delay(10.seconds)
blueFalcon.stopScanning()
}
Device Discovery (Delegates β Flow)
// 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}")
}
}
}
Connecting
// 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}")
}
}
Reading Characteristics
// 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()}")
}
Writing Characteristics
// 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))
}
Common Migration Patterns
Pattern 1: Scanning for Devices
// 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)
}
}
Pattern 2: Connect and Read
// 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}")
}
}
Pattern 3: Monitoring Connection State
// 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 -> {}
}
}
}
Platform-Specific Considerations
Android
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)
iOS
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>
macOS
Dependencies:
macosMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-macos:3.0.0")
}
JavaScript (Web Bluetooth)
Dependencies:
jsMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-js:3.0.0")
}
Browser Support: Chrome, Edge, Opera (same as 2.x)
Windows
Dependencies:
windowsMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-windows:3.0.0")
}
Requirements: Windows 10 1803+ (same as 2.x)
Raspberry Pi
Dependencies:
rpiMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-rpi:3.0.0")
}
Plugin Usage
One of the biggest advantages of 3.0 is the plugin system. Add powerful features with just a few lines:
Logging Plugin
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
Retry Plugin
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")
}
}
Caching Plugin
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!
Multiple Plugins
Combine plugins for powerful functionality:
val blueFalcon = BlueFalcon {
engine = AndroidBlueFalconEngine(context)
install(LoggingPlugin) {
level = LogLevel.INFO
}
install(RetryPlugin) {
maxRetries = 3
}
install(CachingPlugin) {
cacheSize = 10
}
}
FAQ
Q: Do I need to change my existing 2.x code?
A: No! Use the blue-falcon-legacy module for zero-change migration. Your 2.x code works as-is.
Q: Can I use both 2.x and 3.0 APIs in the same app?
A: Yes! The legacy and modern APIs can coexist. Great for gradual migration.
Q: What if I'm using a custom platform?
A: Create a custom engine by implementing BlueFalconEngine. See PLUGIN_DEVELOPMENT_GUIDE.md.
Q: Are plugins required?
A: No, plugins are optional. Core functionality works without any plugins.
Q: How do plugins affect performance?
A: Minimal impact. Plugins use interceptor pattern with negligible overhead.
Q: Can I create custom plugins?
A: Absolutely! See PLUGIN_DEVELOPMENT_GUIDE.md for details.
Q: What happens to my 2.x code when I upgrade?
A: With blue-falcon-legacy, it continues working exactly as before. The legacy module wraps the new engine architecture.
Q: Do I need all platform engines?
A: No! Only include engines for platforms you target. Gradle resolves the correct one automatically in multiplatform projects.
Q: Is 3.0 stable?
A: Yes! 3.0 has been extensively tested and is production-ready. The legacy API ensures backward compatibility.
Q: Where can I find more examples?
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 demonstration
Q: How do I report issues?
A: Open an issue on GitHub with:
- Blue Falcon version
- Platform (Android, iOS, etc.)
- Code snippet
- Error message/stack trace
Next Steps
- Choose your migration strategy (we recommend Strategy 1 for existing apps)
- Update dependencies in your
build.gradle.kts - Test thoroughly on all your target platforms
- Explore plugins to enhance functionality
- Read more documentation:
Welcome to Blue Falcon 3.0! π