TL;DR: flutter_blue_plus is fully capable of holding several BLE connections at once — each BluetoothDevice is connected independently and exposes its own connectionState stream. The hard parts are tracking per-device state, running GATT operations in parallel with setOperationQueueMode(OperationQueueMode.perDevice), and respecting the platform's concurrent-connection ceiling (roughly 7 on most Android phones). Get those three right and a multi-device app is very manageable.
Connecting to a single sensor is a solved problem. You scan, you tap a result, you call connect(), you read a characteristic, and everyone is happy. Then a real product requirement lands on your desk: a wearable and a chest strap, a bank of environmental sensors on a factory floor, or a hub app that talks to every smart bulb in the room at the same time. Suddenly the tidy single-device tutorial you copied doesn't scale, and the code turns into a tangle of stream subscriptions that never get cancelled.
The good news is that the BLE central role — the role your phone plays — is designed for exactly this. A single mobile radio can maintain multiple simultaneous connections. The bad news is that most sample code assumes one global "connected device," so you have to build the plumbing that keeps N devices, N state streams, and N sets of characteristics from stepping on each other.
This guide walks through the architecture and the specific flutter_blue_plus APIs that make multi-device work reliable, plus the platform limits that will bite you if you ignore them.
How multiple connections actually work
There is no special "multi-connect" call. In flutter_blue_plus every device is just a BluetoothDevice object, and you connect to each one separately. What changes at scale is ownership of state: instead of a single _connectedDevice field, you keep a collection.
The cleanest mental model is a registry keyed by the device's stable identifier, remoteId (a DeviceIdentifier). That id is stable for a given peripheral, so it makes a perfect map key.
// A minimal registry keyed by each device's remoteId.
final Map<DeviceIdentifier, BluetoothDevice> _devices = {};
final Map<DeviceIdentifier, StreamSubscription> _connSubs = {};
void track(BluetoothDevice device) {
_devices[device.remoteId] = device;
}
Note that connectionState is described in the API as the connection state of your app to that device — not the system-wide state. That distinction matters: another app (or the OS) may hold a connection you don't see, which is why flutter_blue_plus separates FlutterBluePlus.connectedDevices (connected to your app) from FlutterBluePlus.systemDevices(...) (connected to the system by any app).
Connecting to several devices
You can connect either sequentially or in parallel. Firing every connect() at once looks appealing, but on Android concurrent connection attempts have historically been fragile, so a short stagger is safer for the initial burst.
Future<void> connectAll(List<BluetoothDevice> targets) async {
for (final device in targets) {
// Kick off the connection; listen before it completes.
device.connect(timeout: const Duration(seconds: 15));
_listenTo(device);
await Future.delayed(const Duration(milliseconds: 300));
}
}
Two things worth calling out. First, connect() in flutter_blue_plus 2.x takes a required license: argument that older 1.x releases didn't have — check the current README for the value your project should pass. Second, if you set autoConnect: true, connect() returns immediately and the device reconnects whenever it is seen again; you then rely entirely on the connectionState stream to know when the link is actually up. That is ideal for a fleet of devices you want to opportunistically re-establish. (For the full reconnection story, see the auto-reconnect guide linked below.)
Watching every device at once
Each connection has its own connectionState stream, and you should subscribe per device so you can react to a single peripheral dropping without touching the others. The trick that prevents leaks is cancelWhenDisconnected, which ties a subscription's lifetime to the device.
void _listenTo(BluetoothDevice device) {
final sub = device.connectionState.listen((state) {
if (state == BluetoothConnectionState.connected) {
// Discover services for THIS device only.
device.discoverServices();
} else if (state == BluetoothConnectionState.disconnected) {
_cleanup(device);
}
});
// Auto-cancel this subscription when the device disconnects.
device.cancelWhenDisconnected(sub, delayed: true, next: true);
}
When you'd rather have one firehose for the whole fleet — for logging, analytics, or a dashboard — flutter_blue_plus exposes a global event bus. FlutterBluePlus.events.onConnectionStateChanged emits an event containing both the device and its new connectionState, so you don't have to fan out N subscriptions just to observe.
FlutterBluePlus.events.onConnectionStateChanged.listen((event) {
debugPrint('${event.device.remoteId}: ${event.connectionState}');
});
Building a production multi-device app? The complete, battle-tested connection manager — a per-device state machine with reconnection, back-pressure, MTU handling, and clean teardown wired into Riverpod — is exactly what we build step by step in the BLE Flutter course. This article shows the shape; the course ships the whole thing.
Running operations in parallel (the setting almost everyone misses)
Here is the single biggest performance lever for multi-device apps. By default flutter_blue_plus uses a global operation queue: every read, write, and notification-subscribe across all devices runs one at a time. That's safe, but it means a slow write to a sluggish sensor blocks a snappy read on a completely different device.
Switching to per-device queueing lets operations on different devices run concurrently:
// Call this ONCE at startup, before any other BLE work.
FlutterBluePlus.setOperationQueueMode(OperationQueueMode.perDevice);
Two caveats from the docs: OperationQueueMode.global remains the default for backward compatibility, and you must set the mode before starting any BLE work — changing it mid-flight throws. You can inspect the current setting via FlutterBluePlus.operationQueueMode.
Connection limits and the shared radio
Multi-device BLE has a physics problem: you have one antenna. Two limits follow from that.
Concurrent connection count. Android caps concurrent GATT connections — commonly around 7 (BTA_GATTC_CONN_MAX in the Bluetooth stack), though the real number varies by chipset and manufacturer, and some devices allow fewer. iOS doesn't publish a hard figure but is similarly bounded in practice. Design for a realistic ceiling and handle the "connection refused" case rather than assuming unlimited slots.
Rule of thumb: if you need to talk to dozens of peripherals, you almost never keep them all connected. You connect, exchange data, and disconnect in rotation. Persistent connections are for the handful of devices that need live streaming.
Shared bandwidth. All connections share the radio's air time, so aggregate throughput is divided among them. iOS transfers roughly 4 packets per connection event and Android around 6; the more devices you hold, the less bandwidth each one gets. If several peripherals stream at once, raise the connection interval or accept higher latency — you can nudge Android's behaviour with requestConnectionPriority(...), and tune per-device throughput with MTU (see the MTU guide below).
Common pitfalls and gotchas
- Leaked subscriptions. The number-one multi-device bug. Every device you track adds
connectionState, characteristic, and MTU listeners. If you don't cancel them on disconnect, you get duplicate callbacks and memory growth. Lean on device.cancelWhenDisconnected(...).
- A single "connected device" field. Refactor to a
Map keyed by remoteId early. Retrofitting collection semantics later is painful.
- Forgetting per-device queue mode. If throughput feels serialized across devices, you almost certainly left the queue in
global mode.
- Discovering services globally. Service and characteristic references belong to one device. Store them per device; never reuse a characteristic object from device A on device B.
- Assuming unlimited connections. Wrap
connect() in error handling and surface a clear message when you hit the platform ceiling.
- Concurrent connect storms on Android. Stagger the initial connections slightly instead of firing them all in the same tick.
Related Guides
FAQ
How many BLE devices can a Flutter app connect to at once?
It depends on the platform and hardware, not on flutter_blue_plus. Most Android phones allow around 7 concurrent GATT connections; iOS is bounded but doesn't publish a number. Treat the ceiling as a design constraint and handle failures when you exceed it.
Do I need a separate library for multiple connections?
No. flutter_blue_plus handles multiple simultaneous connections natively. Each device is an independent BluetoothDevice with its own connection lifecycle and streams.
How do I get a list of everything currently connected?
Use FlutterBluePlus.connectedDevices for devices connected to your app (a synchronous snapshot), or FlutterBluePlus.systemDevices(withServices) for devices connected to the system by any app.
Why are my reads and writes slow when several devices are connected?
Most likely you're on the default global operation queue, which serializes every operation across all devices. Call FlutterBluePlus.setOperationQueueMode(OperationQueueMode.perDevice) at startup. Also remember the radio's bandwidth is shared, so raw throughput per device drops as you add connections.
Should I connect to all devices in parallel or one at a time?
A brief stagger for the initial batch is safest on Android. After the first connections settle, per-device queue mode lets their ongoing operations run concurrently.
Summary
Multi-device BLE in Flutter isn't a different API — it's the same flutter_blue_plus primitives applied with discipline. Track devices in a map keyed by remoteId, give each connection its own connectionState subscription with cancelWhenDisconnected for cleanup, switch to OperationQueueMode.perDevice so operations run in parallel, and design around the platform's connection and bandwidth limits. Nail those and adding the fifth device is no harder than adding the second.
When you're ready to turn these fragments into a robust, production-grade connection manager — reconnection, per-device MTU, structured state, and a clean architecture that scales — that's exactly what we build in the BLE Flutter course. Stop stitching together Stack Overflow snippets and learn the whole system end to end.