flutter_blue_plus: Read, Write & Notify in Flutter

<\/script>\n
'; }, get iframeSnippet() { const domain = '{ SITE_DOMAIN }'; const type = '{ embed_type }'; const slug = '{ embed_slug }'; return ''; }, get activeSnippet() { return this.method === 'script' ? this.scriptSnippet : this.iframeSnippet; }, copySnippet() { navigator.clipboard.writeText(this.activeSnippet).then(() => { this.copied = true; setTimeout(() => { this.copied = false; }, 2000); }); } }" @keydown.escape.window="open = false" @click.outside="open = false">

Embed This Widget

Theme


      
    

Widget powered by . Free, no account required.

Building cross-platform BLE apps with Flutter

| 3 min read

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.

Frequently Asked Questions

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.