Flutter BLE App Development: What It Takes

Published on April 9, 2026

14 min read
Flutter BLE companion app connected to cycling hardware
Jonas Ockerman
Jonas OckermanCo-Founder

You’ve got a hardware product and you need an app to go with it. Simple enough on the surface. But the moment Bluetooth Low Energy enters the picture, the scope expands in ways that aren’t obvious until you’re already three months in.

We learned this building the companion app for Classified Cycling, a Belgian company making wireless drivetrain technology for cyclists. Their hardware connects over BLE, needs over-the-air firmware updates, and runs in outdoor environments where Bluetooth behaves unpredictably. The app now manages connections to four distinct hardware types, integrates with two external partner ecosystems, and handles firmware distribution for all of them. What started as “a pairing screen and some settings” became a fully engineered connectivity platform.

This post covers what BLE companion apps actually require, why Flutter handles them well, where the real complexity sits, and the questions worth asking before you start.

What BLE Companion Apps Actually Do

A companion app is the mobile interface to a hardware device. For most products, that means four things:

Pairing and device discovery. The app scans for nearby BLE peripherals, identifies the right device (usually by manufacturer-specific advertising data), authenticates with it, and stores the bond. On iOS, the system manages bonding state. On Android, you handle it yourself. These behave differently enough that they’re effectively two separate engineering problems.

Configuration. Once paired, the app reads and writes configuration values from BLE characteristics. For Classified Cycling, this means shifter button assignments, ride modes, and shift thresholds. Each configurable parameter is a GATT characteristic with a UUID, a data type, and read/write permissions.

Live telemetry. Some devices broadcast data continuously. The Classified hub sends battery level, connection status, and shift events as BLE notifications. The app subscribes to these characteristics and updates the UI in real time. The challenge is doing this reliably while the phone is in a jersey pocket, the hub is on a bike frame, and there’s a peloton of carbon fiber and metal between them.

OTA firmware updates. This is the hardest part. Pushing firmware over BLE means transferring several hundred kilobytes over a slow, lossy channel in a way that cannot brick the device if interrupted. Most hardware uses Nordic Semiconductor’s Device Firmware Update protocol (Nordic DFU) or similar. The app orchestrates the transfer, handles retries, and must recover gracefully if the connection drops mid-update.

Who shapes the BLE landscape teams build on
Nordic Semiconductor
DFU and chips found in countless peripherals
Apple Core Bluetooth
iOS bonding, background modes, and store rules
Android Bluetooth stack
Scan limits, permissions, and OEM power policies

Why Flutter Works for BLE

Flutter’s BLE ecosystem has consolidated around two well-maintained packages: flutter_reactive_ble and flutter_blue_plus. Both cover the full connection lifecycle. Both support iOS and Android from a single codebase.

That single-codebase point matters more than it might sound. BLE on iOS and Android diverges at the OS level. If you build separate native apps, you’re writing and maintaining two separate BLE stacks. With Flutter, you write the BLE layer once and handle platform differences in a contained layer rather than across two codebases.

The plugin ecosystem is also mature for specific use cases. Nordic DFU has a well-supported Flutter package. iBeacon detection, GATT profiles for common hardware categories, background scan management - these are solved problems with maintained packages, not things you build from scratch.

For a decision-maker: Flutter for BLE means a single team, a single codebase, and an ecosystem with enough depth to cover most companion app requirements out of the box. The realistic alternative - separate native iOS and Android teams - costs more and takes longer for functionally equivalent results.

Flutter BLE stacks vs dual native codebases
flutter_reactive_ble
Stream-oriented API
One Dart layer
Codebase
iOS + Android
Platforms
Reactive pipelines fit your app
Best when
flutter_blue_plus
Widely adopted alternative
One Dart layer
Codebase
iOS + Android
Platforms
Callback-style fits the team
Best when
Native iOS + Android
Separate implementations
Swift + Kotlin BLE
Codebase
Duplicated logic
Platforms
Non-Flutter legacy constraints
Best when

Where the Real Complexity Lives

The pairing screen is the easy part. Here’s what actually takes time.

iOS and Android bond differently. iOS manages BLE bonding at the OS level. Android exposes it at the app level. On iOS, if the user reinstalls the app, the OS still remembers the bond but the app’s stored device ID is gone, so the app can’t find its own paired devices without re-scanning. On Android, you manage bonding yourself. These edge cases need explicit handling.

Outdoor Bluetooth conditions. The Classified app runs on bikes. The phone is in a jersey pocket, the hub is under the saddle, and the rider is moving at 40 km/h through a field of carbon and metal. BLE signal doesn’t propagate reliably in these conditions. The app needs automatic reconnection logic, connection quality monitoring, and a state machine that knows the difference between “temporarily disconnected” and “user walked away.”

The BLE connection lifecycle. A BLE connection isn’t a socket. It has states: scanning, connecting, discovering services, authenticating, reading characteristics, receiving notifications, and disconnecting. Each state can fail, and recovery from each failure is different. Managing this correctly requires an explicit state machine, not a series of callbacks. Here’s what the lifecycle looks like for Classified:

01Scan for peripherals matching the device manufacturer prefix
02Connect and discover GATT services and characteristics
03Authenticate with a challenge-response handshake over an encrypted characteristic
04Subscribe to notification characteristics for telemetry and status
05Read and write configuration characteristics as the user interacts
06On disconnect, determine if it was user-initiated or a drop, and act accordingly

Background processing. Both iOS and Android restrict what apps can do in the background. If the app needs to maintain a BLE connection while the phone screen is off (common for fitness and cycling apps), you need to handle background execution modes explicitly. iOS’s Core Bluetooth background modes require capability declarations and have restrictions on what you can read. Android background scan restrictions tightened in Android 8 and again in 12.

Dart isolates for scan loops. Continuous BLE scanning from the main Dart isolate affects UI performance. The right pattern is to run the scan loop in a separate Dart isolate, communicating scan results back to the UI isolate via message passing. This is the same pattern we use for blocking native operations in our Flutter FFI work.

The firmware update window. An OTA update takes 2-5 minutes on a typical BLE link. During that time, the app cannot lose connection, the phone battery cannot die, and the update cannot be interrupted by the OS. That’s not a UI problem, it’s a reliability engineering problem.

None of these are insurmountable. But each one requires deliberate engineering, and the time adds up. If your team hasn’t shipped a BLE companion app before, the estimate for “the BLE layer” is probably 40-60% lower than what it should be.

BLE App vs Web Interface

Not every connected device needs a native companion app. Web Bluetooth exists and works in Chromium-based browsers. A web dashboard can cover configuration and status display for many use cases. Here’s how to choose:

RequirementNative AppWeb Bluetooth / Web Dashboard
OTA firmware updatesYesLimited (file transfer only)
Background BLE connectionYesNo
iOS supportYesNo (Apple blocks Web BLE on iOS)
Offline useYesPartial
App store distributionRequiredNot required
Push notificationsYesLimited
Complex GATT profilesYesYes
Fast time to first versionSlowerFaster

Web Bluetooth only runs in Chrome and Edge. It does not run on iOS at all. If your users are on iPhones, a web interface is not a complete substitute for an app.

For configuration-only use cases where Android is your only platform and users are technical enough to use a browser, Web Bluetooth is reasonable. For anything involving iOS, background connectivity, OTA updates, or consumer-facing distribution, you need a native app.

Choose a native app when
You need OTA firmware updates with robust retry and recovery
The product must stay connected in the background with the screen off
Your users are on iPhones or you need App Store distribution
Push notifications or a consumer install flow matter for adoption
Web Bluetooth can be enough when
Configuration-only workflows on Android, with technical users in Chrome or Edge
Sessions are short and the tab can stay open, no background link required
You are optimizing for the fastest first version and browser-only reach is acceptable

What to Ask Before Starting

These are the questions we ask in every discovery call for a BLE companion app project. If you’re commissioning one, having clear answers to these before you start will save time and prevent scope surprises.

How many device types? Each device type is a separate BLE implementation. A hub, a sensor, and a remote that all need to be managed from one app are three separate connection stacks, not one.

What BLE profiles are you using? Standard GATT profiles (Heart Rate, Battery, Device Information) have existing Flutter packages. Custom profiles require protocol implementation work. Shimano’s D-Fly protocol, for example, required us to implement their proprietary pairing sequence from scratch. We covered this in detail in the Shimano DI2 integration post.

Is OTA firmware update required? This doubles the complexity of the BLE layer. Not metaphorically. The retry logic, transfer orchestration, and recovery handling for OTA is roughly equal in engineering effort to everything else combined.

Does the app need to maintain a BLE connection in the background? If yes, add background execution mode handling for both platforms, plus testing on the full range of Android manufacturer variants (Samsung, Xiaomi, and Huawei all implement battery optimization differently).

What does outdoor use look like? Consumer electronics scenarios (home setup, occasional configuration) have very different reliability requirements than sports and fitness scenarios (constant movement, variable signal, safety-critical in some cases).

Are there regulatory requirements? Medical devices, devices that interact with safety-critical systems, and devices sold in regulated categories (automotive, aviation, medical) have certification requirements that affect the BLE implementation.

What’s the target platform split? If your user base is 80% iOS, that changes prioritization. If you need Android 8 support, that affects which APIs are available. If you need to ship on both platforms in three months, the team size and scope need to reflect that.

Key Takeaways

The Classified app started as a pairing screen. Four years and multiple hardware generations later, it manages connections to four device types, integrates with two partner ecosystems, and runs in conditions most connectivity code was never designed for. That arc is typical.

BLE companion apps are not apps that happen to use Bluetooth. The connectivity layer is the product. Design and build it that way from the start.

Flutter’s single codebase advantage is real for BLE projects. The consolidation of the library ecosystem and the ability to share connection logic across iOS and Android reduces ongoing maintenance cost materially.

OTA firmware updates are not a late-stage feature. If they’re required, scope them from the beginning. The architecture decisions that make OTA safe are hard to retrofit.

The questions in the last section are not exhaustive, but any team that cannot answer them clearly before starting is going to discover scope mid-project. Discovery calls exist for a reason.

If you’re building a team that has migrated from React Native, the BLE story in Flutter is one of the clearest wins. We cover the migration decision in more depth in our React Native to Flutter migration post. If BLE is your primary driver, Flutter is the right choice and the migration is worth the investment.

Flutter’s ecosystem covers more than BLE. If you’re also evaluating Flutter for e-commerce use cases, such as scan-to-order workflows that use Bluetooth barcode scanners, our Flutter Magento integration post covers how the two patterns intersect.

Need help with BLE app development?

Frequently Asked Questions