navideck/universal_ble/universal-ble-migrate-from-flutter-blue-plus

Claude Code and agent skill by navideck/universal_ble

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Migrate a Flutter app from flutter_blue_plus (FlutterBluePlus) to universal_ble. Use when the user wants to replace flutter_blue_plus, port BLE scanning, connecting, service discovery, read/write or subscription code, or map FlutterBluePlus APIs (BluetoothDevice, Guid, ScanResult, BluetoothCharacteristic, BluetoothDescriptor) to universal_ble equivalents.

Install

npx vskill@latest install navideck/universal_ble/universal-ble-migrate-from-flutter-blue-plus

SKILL.md

Migrating from flutter_blue_plus to universal_ble

This skill guides a mechanical, central-role migration from flutter_blue_plus (FlutterBluePlus, "FBP") to universal_ble (UniversalBle, "UB"). Both are Flutter BLE central-role plugins; most concepts map 1:1, but the object model and a handful of APIs differ.

Migration workflow

Work top to bottom, keeping the project compiling after each step:

  1. Swap the dependency and imports.
  2. Port adapter/availability state handling.
  3. Port scanning.
  4. Port connecting and service discovery.
  5. Port read/write and descriptor access.
  6. Port notifications/indications.
  7. Replace FBP error handling with UB's typed exceptions.
  8. Update platform setup (Windows especially).
  9. Run flutter analyze and fix remaining references to flutter_blue_plus.

Search the codebase for FlutterBluePlus, BluetoothDevice, BluetoothCharacteristic, BluetoothDescriptor, ScanResult, Guid, and import 'package:flutter_blue_plus.

1. Dependency and imports

Remove flutter_blue_plus (and flutter_blue_plus_winrt if present) and add universal_ble:

yaml
dependencies:
universal_ble: ^3.0.0

Replace imports:

dart
// Before
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
// After
import 'package:universal_ble/universal_ble.dart';

Windows previously required the separate flutter_blue_plus_winrt package; UB supports Android, iOS, macOS, Windows, Linux, and Web from the single package.

2. Mental model

Conceptflutter_blue_plusuniversal_ble
Entry pointFlutterBluePlus (static)UniversalBle (static)
Device handleBluetoothDevice objectBleDevice (high level) or a String deviceId (low level)
IdentifiersGuid objects (.str)Plain String UUIDs, any format/case
ResultsScanResult wrappersBleDevice directly
Device operationsMethods on BluetoothDeviceExtension methods on BleDevice and/or UniversalBle statics

Key differences to internalize:

  • UUIDs are plain strings. No Guid wrapper. Pass "180D", "180d", or the

full 128-bit form interchangeably; UB normalizes everything to lowercase 128-bit (e.g. 0000180d-0000-1000-8000-00805f9b34fb). Use BleUuidParser.string(), BleUuidParser.number(), or BleUuidParser.compare() if you need explicit conversion.

  • Two styles. Prefer the high-level BleDevice / extension API (closest to FBP).

The low-level UniversalBle.read(deviceId, service, characteristic, ...) statics are for deviceId-only workflows. Do not mix object methods with a device you never obtained as a BleDevice.

  • Streams never close and never emit errors. Cancel subscriptions yourself.

3. Symbol mapping

Adapter / availability
flutter_blue_plusuniversal_ble
FlutterBluePlus.adapterState (Stream)UniversalBle.availabilityStream (Stream)
FlutterBluePlus.adapterStateNowawait UniversalBle.getBluetoothAvailabilityState()
BluetoothAdapterState.onAvailabilityState.poweredOn
BluetoothAdapterState.offAvailabilityState.poweredOff
BluetoothAdapterState.unauthorizedAvailabilityState.unauthorized
BluetoothAdapterState.unavailableAvailabilityState.unsupported
BluetoothAdapterState.unknownAvailabilityState.unknown
FlutterBluePlus.turnOn() / turnOff()UniversalBle.enableBluetooth() / disableBluetooth() (Android/Windows/Linux; returns bool)
FlutterBluePlus.isSupportedNo direct API. Treat AvailabilityState.unsupported as unsupported
FlutterBluePlus.setLogLevel(LogLevel.x)UniversalBle.setLogLevel(BleLogLevel.x)
FlutterBluePlus.setOperationQueueMode(...)`UniversalBle.queueType = QueueType.autoglobalperDevicenone`
FlutterBluePlus.setOptions(restoreState: true)No direct API. iOS state restoration follows the bluetooth-central background mode automatically
FlutterBluePlus.setLogLevel logs streamNo equivalent; use the platform logger

FBP's initial adapter state is often unknown on iOS; UB reports unknown too, so keep any "wait for poweredOn" logic.

Scanning
flutter_blue_plusuniversal_ble
FlutterBluePlus.startScan(withServices: [Guid], withNames:, withRemoteIds:, timeout:, androidUsesFineLocation:)UniversalBle.startScan(scanFilter: ScanFilter(withServices: [...], withNamePrefix: [...]), platformConfig: ...)
FlutterBluePlus.stopScan()UniversalBle.stopScan()
FlutterBluePlus.onScanResults / scanResults (Stream<List<ScanResult>>)UniversalBle.scanStream (Stream<BleDevice>, one device per event)
FlutterBluePlus.lastScanResultsNo equivalent; keep your own cache
FlutterBluePlus.isScanning / isScanningNowUniversalBle.isScanning()
FlutterBluePlus.cancelWhenScanComplete(sub)Manually sub.cancel()
ScanResult.devicethe BleDevice event itself
ScanResult.rssibleDevice.rssi
ScanResult.advertisementData.advNamebleDevice.name
ScanResult.advertisementData.serviceUuidsbleDevice.services
ScanResult.advertisementData.manufacturerDatableDevice.manufacturerDataList (ManufacturerData)
ScanResult.advertisementData.serviceDatableDevice.serviceData
Guid("180D") in filters"180D"

Notable differences:

  • No `timeout:` parameter. Implement the FBP timeout yourself:

``dart UniversalBle.startScan(scanFilter: ScanFilter(withServices: ['180D'])); Timer(const Duration(seconds: 15), () => UniversalBle.stopScan()); ``

  • No list batching. scanStream emits one BleDevice per result, not a list.

There is no "clears between scans" distinction; maintain your own list if needed.

  • No `withRemoteIds`. Filter scan results in Dart, or use ScanFilter.exclusionFilters.
  • Android fine location: pass platformConfig: PlatformConfig(android: AndroidOptions(requestLocationPermission: true)) instead of androidUsesFineLocation:.
Devices
flutter_blue_plusuniversal_ble
BluetoothDevice.fromId(id)BleDevice(deviceId: id, name: null) or call UniversalBle.* statics with id
device.remoteId.strbleDevice.deviceId
device.platformNamebleDevice.name (sanitized) or bleDevice.rawName (raw)
device.advNamebleDevice.name
device.connect(mtu:, autoConnect:)bleDevice.connect(autoConnect: ...); call requestMtu() separately
device.disconnect()bleDevice.disconnect()
device.isConnected (sync)await bleDevice.isConnected (async)
device.isDisconnected!await bleDevice.isConnected
device.connectionState (Stream enum)bleDevice.connectionStream (Stream<bool>)
device.connectionState snapshotawait bleDevice.connectionState returns BleConnectionState
device.disconnectReasonBleConnectionChange.errorCode / .error from bleDevice.connectionChangeStream
device.discoverServices()await bleDevice.discoverServices()
device.servicesListNo equivalent; the list returned by discoverServices() is cached — use getService()/getCharacteristic()
device.onServicesResetNo equivalent
device.mtu / mtuNowNo stream; await bleDevice.requestMtu(n) returns the negotiated value
device.readRssi()await bleDevice.readRssi()
device.requestMtu(n)await bleDevice.requestMtu(n)
device.requestConnectionPriority(ConnectionPriority.high)UniversalBle.requestConnectionPriority(deviceId, BleConnectionPriority.highPerformance)
device.createBond() / removeBond() / bondStatebleDevice.pair() / unpair() / pairingStateStream
device.clearGattCache()discoverServices(platformConfig: DiscoverServicesPlatformConfig(android: AndroidDiscoverServicesOptions(clearGattCache: true)))
device.cancelWhenDisconnected(sub)Manually sub.cancel() when the connection stream goes false
FlutterBluePlus.connectedDevicesNo equivalent; track the devices your app connected to
FlutterBluePlus.systemDevices(withServices)UniversalBle.getSystemDevices(withServices: [...])

getSystemDevices on Apple requires withServices; without it UB defaults to generic 18XX services. You must still connect() to system devices before use.

Characteristics and descriptors
flutter_blue_plusuniversal_ble
characteristic.uuid.strcharacteristic.uuid
characteristic.properties.readcharacteristic.properties.contains(CharacteristicProperty.read)
characteristic.read()await characteristic.read()
characteristic.write(v)await characteristic.write(v) (with response by default)
characteristic.write(v, withoutResponse: true)await characteristic.write(v, withResponse: false)
characteristic.setNotifyValue(true)await characteristic.notifications.subscribe()
characteristic.setNotifyValue(false)await characteristic.unsubscribe()
characteristic.isNotifyingcharacteristic.isSubscribed
characteristic.onValueReceived (Stream<List<int>>)characteristic.onValueReceived (Stream<Uint8List>)
characteristic.lastValue / lastValueStreamNo equivalent; use onValueReceived (it fires on read() too)
characteristic.descriptorscharacteristic.descriptors
descriptor.read() / descriptor.write()characteristic.descriptor(uuid).read() / .write(Uint8List)
Boolean inversion: FBP uses withoutResponse:; UB uses withResponse:.
FBP's write(data) (no flag) means with response; UB's write(data) also
defaults to withResponse: true, so a direct call ports unchanged.

For indications, use characteristic.indications.subscribe() instead of notifications.subscribe().

Events API

FBP's global FlutterBluePlus.events.* streams map to UB streams/handlers:

flutter_blue_plusuniversal_ble
events.onConnectionStateChangedUniversalBle.onConnectionChange = (BleConnectionChange change) {} or UniversalBle.connectionChangeStream(deviceId)
events.onMtuChangedNo equivalent stream
events.onReadRssiUniversalBle.readRssi(deviceId)
events.onServicesResetNo equivalent
events.onDiscoveredServicesNo equivalent
events.onCharacteristicReceivedUniversalBle.onValueChange = (BleCharacteristicValue value) {}
events.onCharacteristicWrittenNo equivalent
events.onDescriptorRead / onDescriptorWrittenNo equivalent
events.onBondStateChangedUniversalBle.onPairingStateChange = (deviceId, isPaired) {}
events.onNameChangedNo equivalent

Prefer the per-device/stream form (scanStream, connectionStream, characteristicValueStream, stream.listen) over the global onX handlers when you already hold the relevant object.

4. Before / after examples

Adapter state
dart
// Before (flutter_blue_plus)
FlutterBluePlus.adapterState.listen((state) {
if (state == BluetoothAdapterState.on) startScanning();
});
// After (universal_ble)
UniversalBle.availabilityStream.listen((state) {
if (state == AvailabilityState.poweredOn) startScanning();
});
Filtered scan
dart
// Before
FlutterBluePlus.onScanResults.listen((results) {
for (final r in results) {
print('${r.device.remoteId} ${r.advertisementData.advName}');
}
});
await FlutterBluePlus.startScan(
withServices: [Guid('180D')],
withNames: ['Bluno'],
timeout: const Duration(seconds: 15),
);
// After
UniversalBle.scanStream.listen((BleDevice d) {
print('${d.deviceId} ${d.name}');
});
await UniversalBle.startScan(
scanFilter: ScanFilter(withServices: ['180D'], withNamePrefix: ['Bluno']),
);
Timer(const Duration(seconds: 15), () => UniversalBle.stopScan());
Connect and discover services

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
171
Repository forks
77
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/navideck/universal_ble/universal-ble-migrate-from-flutter-blue-plus/badge)](https://verified-skill.com/skills/navideck/universal_ble/universal-ble-migrate-from-flutter-blue-plus)

Something wrong with this skill or its page? Tell us.