# flutter_blue_plus: cross-platform BLE for Flutter with a licence to read

> A Flutter Bluetooth Low Energy plugin covering iOS, macOS, Android, Linux and the Web with no third-party dependencies, built as a continuation of the older FlutterBlue project. Two things deserve a close look before adoption: it is Central role only, and the licence is not open source, requiring a purchased commercial licence for for-profit use.

**chipweinberger/flutter_blue_plus** — Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android, iOS, macOS, Web, Linux, Windows.

- Repository: https://github.com/chipweinberger/flutter_blue_plus
- Stars: 1,007 · Forks: 622
- Language: Dart
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/chipweinberger-flutter-blue-plus

## What flutter_blue_plus does and where it came from

flutter_blue_plus is a Bluetooth Low Energy plugin for Flutter. The repository description lists the platforms as Android, iOS, macOS, Web, Linux and Windows, and the README then qualifies the Windows claim: for Windows you should use the separate `flutter_blue_plus_winrt` package, which is maintained by @chan150.

The lineage matters when you read the API. The README notes that this plugin is continuous work from FlutterBlue, the package by pauldemarco, and points migrating users to a `MIGRATION.md` guide in the repository. A plugin that is a fork with a renamed API keeps a compatibility burden, and the migration guide is where you find out what changed.

The scope is Central role only, described as the most common case, and the README points to `flutter_ble_peripheral` or `bluetooth_low_energy` if you need the Peripheral role. That is an honest scope statement, and it is the kind of thing that saves you from building a device that has to advertise.

Two hard limits are stated just as plainly. Bluetooth Classic is not supported, so the README rules out Arduino HC-05 and HC-06 modules, speakers, headphones, mice, keyboards and gamepads. And iBeacons are not supported on iOS, because Apple requires you to use CoreLocation instead.

The repository tree is small and tells you the project is a monorepo of related packages rather than a single plugin: a `packages/` directory, a `site/` directory, and the documentation files `MIGRATION.md` and `MOCKING.md` alongside `CONTRIBUTING.md`, `LICENSE` and `LICENSE.md`.

## The licence is source-available, not open source

This is the first thing a team should check, because it is unusual for a package of this popularity.

The GitHub metadata classifies the licence as Other, a custom licence that GitHub cannot categorise, and the README says the plugin is licensed under the FlutterBluePlus License, in `LICENSE.md`. The terms are split in two. Nonprofit use is free for personal use, nonprofits and educational institutions, with no licence required. Commercial use, meaning for-profit use by for-profit organisations or individuals, requires a Commercial Licence, which grants lifetime use, covers all FlutterBluePlus packages, and includes lifetime updates.

So the package is free for a lot of projects and paid for a lot of others. The cost model is not a subscription but a one-off purchase tied to the organisation.

The project is sponsored by Jamcorder, and the commercial licence is sold through that company's shop, so the vendor and the sponsor are the same party. That is a straightforward arrangement for a package maintained by one commercial entity, and it explains both the licence and the maintenance commitment: the lifetime updates promise is what makes the purchase defensible.

The practical consequence for an engineering organisation is a procurement conversation rather than a dependency line. A team that assumed the permissive licences of most pub.dev packages will need to talk to whoever owns the budget, and a team shipping a for-profit product needs the commercial licence in place before release, not after.

Read `LICENSE.md` in the repository for the actual terms. This summary is a pointer, not a substitute.

## Zero dependencies is a maintenance argument

The README has a section titled No Dependencies, and the claim is that FlutterBluePlus has zero dependencies besides Flutter, Android, iOS and macOS themselves. The stated consequence is that this makes the plugin very stable and easy to maintain.

The reasoning is sound in a way that is rare in plugin ecosystems. Most cross-platform plugins accumulate dependencies, and each one is a version constraint you have to satisfy, a transitive vulnerability surface, and a thing that can break when the Flutter SDK moves. A plugin that talks to the platform channels directly has none of that.

The cost of the approach is that every platform feature has to be implemented per platform. The README says the plugin supports nearly every feature on all supported platforms: iOS, macOS, Android, Linux and Web. The word nearly is doing work there, and the Web implementation in particular is necessarily a subset of what the native platforms can do, since the browser's Web Bluetooth API is not the BLE stack a phone has.

The project also points new users at BLE tutorials before the API documentation, linking guides from Novel Bits, All About Circuits and Embetronicx, with the advice that if you are new to Bluetooth you should start by reading them. That is a sign of respect for the domain: the plugin is a thin layer over a protocol with a lot of sharp edges, and the sharp edges are not the plugin's fault.

The practical test of the stability claim is straightforward: look at the release history. The recent versions are 2.3.14 from 2026-10-03, 2.3.12 from 2026-08-10 and v2.2.1 from 2026-04-19, and the last push was on 2026-10-03. A plugin that keeps shipping releases and is still receiving commits is behaving as the README describes.

## Error handling as a documented contract

The README says FlutterBluePlus takes error handling seriously, and then states a rule that is unusual enough to be worth quoting exactly: streams returned by FlutterBluePlus never emit any errors and never close. There is no need to handle `onError` or `onDone` for `stream.listen(...)`.

There is one exception, and it is the one that matters operationally: `FlutterBluePlus.scanResults` is the exception, and you should handle `onError` for it. Scanning fails for reasons that connection and characteristic streams do not, and this is where a permission denial or a radio failure shows up.

Underneath that, every error returned by the native platform is checked and thrown as an exception where appropriate. So the design is: one-shot operations throw, long-lived streams are error-free by construction, and the scan stream is the special case.

This is a better contract than the alternative, where a stream occasionally emits an error and every consumer has to write defensive `onError` handlers that rarely fire. Making the streams total and marking the one exception explicitly means the exception carries information.

The second error-handling detail is the adapter state. The README notes that on iOS the initial state is typically `BluetoothAdapterState.unknown`, and that if you have permission issues you will get stuck at `BluetoothAdapterState.unauthorized`. A state machine with an unknown starting value and a distinct unauthorized state is a design you have to handle, not a boolean you can read.

## Scanning, filters and the cost of an unfiltered scan

The scanning API is a stream of results, and there are two of them with different semantics. `onScanResults` clears the results between scans, while `scanResults` gives you the current scan results or the results from the previous scan. That distinction is the kind of thing that produces a bug where a device appears to be missing, so it is worth picking the right one deliberately.

```dart
var subscription = FlutterBluePlus.onScanResults.listen((results) {
        if (results.isNotEmpty) {
            ScanResult r = results.last; // the most recently found device
            print('${r.device.remoteId}: "${r.advertisementData.advName}" found!');
        }
    },
    onError: (e) => print(e),
);

FlutterBluePlus.cancelWhenScanComplete(subscription);
```

The `cancelWhenScanComplete` call is the cleanup idiom: it cancels the subscription when scanning stops, so you do not leak a listener.

The performance note is explicit: it is recommended to set scan filters to reduce main thread and platform channel usage. An unfiltered scan pushes every advertisement the radio sees across the platform channel and into Dart, and on a busy environment that is a lot of traffic for data most applications discard. The plugin gives you the filter mechanism, but it does not add one for you.

For the case where a device is not found, the README points to a Common Problems section in its own documentation, which is the right place to look before assuming a hardware fault.

## The example app is the debugging tool

The README calls the example app a useful tool for debugging issues, and gives the two commands to run it:

```bash
cd ./example
flutter run
```

That is worth taking at face value. When a BLE integration misbehaves, the first question is always whether the plugin or your application is at fault, and a reference application written by the plugin author is the fastest way to answer it. If the example finds your device and yours does not, the problem is in your code.

The plugin also exposes its own logging, which is the diagnostic tool behind the example. Verbose logging shows all data in and out, with the log level configurable and a stream that forwards log lines anywhere you want:

```dart
FlutterBluePlus.setLogLevel(LogLevel.verbose, color:false);

FlutterBluePlus.logs.listen((String s){
    // send logs anywhere you want
});
```

The `color: false` argument is there for a concrete reason: if your terminal does not support colour you will see escape sequences like `\x1B[1;35m` in the output. The log lines are annotated to distinguish the function name, the arguments sent to the platform, and the data coming back, which is what makes the log readable when you are tracing a GATT operation.

There is also a `MOCKING.md` file in the repository, which covers mocking the plugin. For a team writing UI against BLE data before the hardware is available, or writing tests that must not touch a radio, that is a capability rather than a convenience, and it is documented separately from the main README.

## Where flutter_blue_plus is the wrong choice

The clearest wrong case is a for-profit product without a licence purchase. The free tier covers personal use, nonprofits and educational institutions; anything else needs the commercial licence. If your organisation is a company and you ship the app, you are in the paid tier whether or not the code itself is open.

The second wrong case is a device that speaks Bluetooth Classic. The README rules out HC-05 and HC-06 modules, speakers, headphones, mice, keyboards and gamepads. A lot of the cheap Bluetooth devices people want to connect to are Classic, and no amount of configuration changes that.

The third is the Peripheral role. If your phone has to advertise to your device rather than connect to it, this plugin cannot do it, and the README points you to two other packages rather than pretending.

The fourth is iBeacons on iOS, which Apple requires you to handle with CoreLocation. The README says so directly, and that is a platform policy rather than a plugin limitation, so no upgrade will change it.

The fifth is Windows, if you were relying on the repository description. Windows support lives in a separate package, `flutter_blue_plus_winrt`, maintained by someone else. The description lists Windows among the platforms, and the README corrects that, which is a reminder to read past the summary field.

For everyone else, the practical checks before you commit are small: confirm your hardware is a BLE Central target, read the `MIGRATION.md` if you came from FlutterBlue, and try the example app on the target device before building your own features on top.

## Conclusion

flutter_blue_plus fits Flutter teams building a BLE Central application that need the same API across iOS, Android, macOS, Linux and the Web, and that value a plugin with no third-party dependencies behind it. It does not fit a for-profit product without a budget for a commercial licence, since the free tier covers personal, nonprofit and educational use only, and it does not fit a product that needs Bluetooth Classic, the Peripheral role, or iBeacons on iOS. Before adopting it, read `LICENSE.md` rather than assuming the permissive licences most pub.dev packages carry, confirm that your device is a BLE Central target, and check the `MIGRATION.md` guide if you are moving from the original FlutterBlue package.

## FAQ

### Is flutter_blue_plus free to use in a commercial app?

Not under the free terms. The FlutterBluePlus License is free for personal use, nonprofits and educational institutions, while for-profit use by companies or individuals requires a commercial licence that grants lifetime use and updates across all FlutterBluePlus packages.

### Does flutter_blue_plus support Bluetooth Classic devices?

No. The README rules out Bluetooth Classic explicitly, listing Arduino HC-05 and HC-06 modules, speakers, headphones, mice, keyboards and gamepads. Those devices need a different approach.

### How do I run the flutter_blue_plus example app?

Use `cd ./example` and then `flutter run`. The README describes the example as a useful tool for debugging issues, so it is the reference point for checking whether a problem lies in the plugin or in your own code.

### Which platforms does flutter_blue_plus support?

The README lists iOS, macOS, Android, Linux and Web as supported. For Windows you need the separate `flutter_blue_plus_winrt` package, which is maintained by @chan150.

### Do I need to handle errors on FlutterBluePlus streams?

Generally no. Streams returned by the plugin never emit errors and never close, so `onError` and `onDone` are unnecessary. The exception is `FlutterBluePlus.scanResults`, for which you should handle `onError`.

## Sources

- [chipweinberger/flutter_blue_plus on GitHub](https://github.com/chipweinberger/flutter_blue_plus)
- [Issues](https://github.com/chipweinberger/flutter_blue_plus/issues)
- [README](https://github.com/chipweinberger/flutter_blue_plus/blob/master/README.md)
- [Releases](https://github.com/chipweinberger/flutter_blue_plus/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/chipweinberger-flutter-blue-plus
