flutter_blue_plus: Read, Write & Notify in Flutter
Building cross-platform BLE apps with Flutter
Flutter BLE Development with flutter_blue_plus
Flutter enables cross-platform BLE apps from a single Dart codebase targeting iOS, Android, macOS, and Windows. The flutter_blue_plus package wraps platform BLE APIs with a reactive Stream-based interface, making it the most widely adopted Flutter BLE library.
Setup
# pubspec.yaml
dependencies:
flutter_blue_plus: ^1.31.0
Android (android/app/src/main/AndroidManifest.xml):
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
iOS (ios/Runner/Info.plist):
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to sensors.</string>
Scanning
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
// Start scanning with service UUID filter
FlutterBluePlus.startScan(
withServices: [Guid("0000180d-0000-1000-8000-00805f9b34fb")], // Heart Rate
timeout: const Duration(seconds: 10),
);
// Listen to scan results as a Stream
final subscription = FlutterBluePlus.onScanResults.listen((results) {
for (ScanResult r in results) {
print('${r.device.remoteId}: ${r.device.platformName} rssi: ${r.rssi}');
}
});
await FlutterBluePlus.isScanning.where((v) => v == false).first;
subscription.cancel();
The RSSI field from scan results feeds proximity estimation. Advertising service data is available via r.advertisementData.serviceData.
Connecting and Service Discovery
BluetoothDevice device = /* from scan results */;
await device.connect(timeout: const Duration(seconds: 15));
// Discover GATT services
List<BluetoothService> services = await device.discoverServices();
for (BluetoothService service in services) {
for (BluetoothCharacteristic c in service.characteristics) {
print('Char: ${c.uuid} props: ${c.properties}');
}
}
GATT service and characteristic UUIDs can be matched against the UUID Generator for standard profiles.
Reading and Writing
// Find characteristic by UUID
BluetoothCharacteristic? findChar(List<BluetoothService> services, String uuid) {
for (var s in services) {
for (var c in s.characteristics) {
if (c.uuid == Guid(uuid)) return c;
}
}
return null;
}
final char = findChar(services, "00002a19-0000-1000-8000-00805f9b34fb"); // Battery Level
// Read
final value = await char!.read();
print("Battery: ${value[0]}%");
// Write
await char.write([0x01, 0x00], withoutResponse: false);
Subscribing to Notifications
const HR_CHAR = "00002a37-0000-1000-8000-00805f9b34fb";
final hrChar = findChar(services, HR_CHAR)!;
await hrChar.setNotifyValue(true); // writes CCCD
final sub = hrChar.onValueReceived.listen((data) {
final bpm = data[0] & 0x01 == 0 ? data[1] : (data[2] << 8) | data[1];
print("BPM: $bpm");
});
// Cleanup
await hrChar.setNotifyValue(false);
sub.cancel();
setNotifyValue(true) writes the CCCD to enable notifications. Indications use the same API — the library handles the ATT-level confirmation.
Connection State and Auto-Reconnect
class BleManager {
StreamSubscription? _stateSub;
void watchDevice(BluetoothDevice device) {
_stateSub = device.connectionState.listen((state) {
if (state == BluetoothConnectionState.disconnected) {
Future.delayed(const Duration(seconds: 2), () => device.connect());
}
});
}
void dispose() => _stateSub?.cancel();
}
Building a Reactive BLE Screen
class HeartRateScreen extends StatelessWidget {
final BluetoothCharacteristic hrChar;
const HeartRateScreen({required this.hrChar, super.key});
@override
Widget build(BuildContext context) => StreamBuilder<List<int>>(
stream: hrChar.onValueReceived,
builder: (ctx, snap) {
final bpm = snap.hasData && snap.data!.isNotEmpty
? snap.data![1].toString()
: '--';
return Text('$bpm bpm', style: Theme.of(ctx).textTheme.displayLarge);
},
);
}
Platform Quirks
| Platform | Notes |
|---|---|
| Android | REQUEST_MTU on connect (Android 5+); background scan restricted in API 31+ |
| iOS | Core Bluetooth limits to 20 concurrent peripherals; address is UUID not MAC |
| macOS | Same Core Bluetooth restrictions as iOS; USB BLE adapter not accessible |
| Windows | WinRT BLE; admin not required, but pairing via OS Settings first for bonded devices |
For OTA firmware updates over BLE from Flutter, use flutter_blue_plus writes combined with MCUboot's SMP protocol for cross-platform DFU support.
자주 묻는 질문
Yes, our guides range from beginner introductions to advanced topics. Each guide indicates its difficulty level and prerequisites so you can find the right starting point.