# How to Integrate External Health Monitoring Devices in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 90-120 min
- Compatibility: FlutterFlow Standard+ (Custom Widget required)
- Last updated: March 2026

## TL;DR

Connect Bluetooth Low Energy (BLE) health monitoring devices to a FlutterFlow app using the flutter_blue_plus package in a Custom Widget. Scan for nearby devices, filter by the GATT service UUID for the device type (0x180D for heart rate, 0x1810 for blood pressure, 0x1808 for glucose), connect to the device, subscribe to characteristic notifications, and parse the incoming data bytes according to the GATT specification. Store readings in Firestore health_readings collection and display in a dashboard. Always implement auto-reconnect logic because BLE devices disconnect frequently.

## Read data from BLE health devices directly in your FlutterFlow app

Medical-grade health monitoring devices — heart rate monitors, blood pressure cuffs, pulse oximeters, glucose meters, and body temperature sensors — communicate over Bluetooth Low Energy (BLE) using standardized GATT (Generic Attribute Profile) profiles. Each device type uses a specific service UUID and transmits data in a defined binary format. Your FlutterFlow app can connect to these devices, subscribe to real-time characteristic updates, and parse the binary data into meaningful readings. This tutorial covers the BLE communication pattern using flutter_blue_plus and the specific parsing logic for the two most common health devices: heart rate monitors (GATT 0x180D) and blood pressure monitors (GATT 0x1810).

## Before you start

- A FlutterFlow project with Standard plan or higher for Custom Code
- A BLE-compatible health monitoring device for testing (or use a heart rate app on a smartwatch that exposes GATT services)
- iOS: add NSBluetoothAlwaysUsageDescription to Info.plist. Android: add BLUETOOTH, BLUETOOTH_ADMIN, and ACCESS_FINE_LOCATION permissions to AndroidManifest.xml

## Step-by-step guide

### 1. Create the BLE device scanner Custom Widget

In FlutterFlow, go to Custom Code → Custom Widgets → Add Widget. Name it BLEDeviceScanner. Add the pubspec dependency flutter_blue_plus: ^1.30.0. The widget scans for nearby BLE devices, filters by the target service UUID, and displays a list of discovered devices. Initialize FlutterBluePlus and call FlutterBluePlus.startScan(withServices: [Guid('0000180D-0000-1000-8000-00805F9B34FB')]) to scan only for heart rate devices — the long form of the service UUID 0x180D. Listen to FlutterBluePlus.scanResults and update a local state list of devices (name + RSSI signal strength). Add a Connect button next to each device. On connect tap: await device.connect(autoConnect: false). After connection, discover services with device.discoverServices(). Find the Heart Rate Measurement characteristic (UUID 0x2A37) and call characteristic.setNotifyValue(true). Listen to characteristic.onValueReceived to get real-time heart rate data bytes.

```
// BLEDeviceScanner - simplified connection + data reading
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

// Heart Rate service UUID and characteristic UUID
const HEART_RATE_SERVICE = '0000180d-0000-1000-8000-00805f9b34fb';
const HEART_RATE_MEASUREMENT = '00002a37-0000-1000-8000-00805f9b34fb';

Future<void> connectAndReadHeartRate(BluetoothDevice device,
    Function(int heartRate) onReading) async {
  await device.connect(autoConnect: false, timeout: Duration(seconds: 15));
  
  final services = await device.discoverServices();
  final hrService = services.firstWhere(
    (s) => s.serviceUuid.toString() == HEART_RATE_SERVICE
  );
  
  final hrChar = hrService.characteristics.firstWhere(
    (c) => c.characteristicUuid.toString() == HEART_RATE_MEASUREMENT
  );
  
  await hrChar.setNotifyValue(true);
  hrChar.onValueReceived.listen((bytes) {
    final heartRate = parseHeartRate(bytes);
    onReading(heartRate);
  });
}

int parseHeartRate(List<int> bytes) {
  // GATT Heart Rate Measurement format:
  // Byte 0: flags. Bit 0 = 0 means HR value is UINT8, = 1 means UINT16
  final isUint16 = (bytes[0] & 0x01) != 0;
  return isUint16 ? (bytes[2] << 8) + bytes[1] : bytes[1];
}
```

**Expected result:** The Custom Widget scans for nearby BLE heart rate devices, shows them in a list with signal strength, and starts streaming heart rate values after the user taps Connect.

### 2. Parse blood pressure readings from a BLE BP monitor

Blood pressure monitors use GATT service UUID 0x1810 (Blood Pressure) and characteristic UUID 0x2A35 (Blood Pressure Measurement). Unlike heart rate which sends continuous notifications, blood pressure sends a single reading after measurement. Set characteristic.setNotifyValue(true) and wait for the onValueReceived event which fires once when the user completes a measurement on the device. Parse the bytes: Byte 0 is the flags byte. Bytes 1-2 are systolic pressure as a SFLOAT (IEEE-11073 16-bit float). Bytes 3-4 are diastolic. Bytes 5-6 are mean arterial pressure. If flag bit 2 is set, bytes 7-8 contain pulse rate. Create a Custom Function parseBPReading(List<int> bytes) that decodes the SFLOAT values: the SFLOAT format uses the upper 4 bits as a signed exponent and lower 12 bits as a mantissa. Return a Map with systolic, diastolic, and pulseRate values.

**Expected result:** After the user takes a measurement on the blood pressure cuff, the parsed systolic/diastolic values and pulse rate appear in the FlutterFlow app within 2 seconds.

### 3. Store readings in Firestore and display a dashboard

After receiving and parsing a health reading in the Custom Widget callback, call a FlutterFlow Action from the Custom Widget using the widget's action callback parameter. The action: (1) creates a Firestore document in health_readings/{userId}/readings with fields: deviceType (String, 'heart_rate' or 'blood_pressure'), value (Map: {heartRate: 75} or {systolic: 120, diastolic: 80}), unit (String, 'bpm' or 'mmHg'), timestamp (Timestamp). Create a HealthDashboard page with a Backend Query on health_readings/{userId}/readings ordered by timestamp descending, limit 100. Display the latest reading prominently (large text widget). Add a Custom Widget using fl_chart LineChart or BarChart for historical trend visualization — bind to the readings list from the Backend Query. Add a 'Connect Device' button that opens a bottom sheet containing the BLEDeviceScanner Custom Widget.

**Expected result:** Readings from the physical BLE device appear immediately in Firestore and update the dashboard chart in real time using the Firestore real-time listener.

### 4. Handle BLE disconnections with auto-reconnect

BLE devices disconnect when: the device goes out of range, the device turns off automatically after a session, the OS kills the BLE connection to save battery, or the measurement is complete (for single-reading devices). In the Custom Widget, listen to device.connectionState stream. When the state changes to BluetoothConnectionState.disconnected: update a local variable connectionStatus to 'Disconnected' and show a red indicator in the UI. Implement auto-reconnect: if autoReconnect app state is true, wait 3 seconds and call connectAndReadHeartRate again. Show a 'Reconnecting...' indicator with a CircularProgressIndicator during the attempt. After 3 failed reconnect attempts, stop retrying and show a Toast: 'Device disconnected. Tap Connect to reconnect.' Display connection status prominently in the UI — a green dot for connected, yellow for reconnecting, red for disconnected.

**Expected result:** When the BLE device disconnects, the app shows a clear status indicator and automatically attempts to reconnect up to 3 times before asking the user to reconnect manually.

## Complete code example

File: `ble_health_devices.txt`

```text
BLE HEALTH DEVICE INTEGRATION IN FLUTTERFLOW

GATT SERVICE UUIDs (common health devices):
├── 0x180D — Heart Rate Service
│   └── 0x2A37 — Heart Rate Measurement (notify)
├── 0x1810 — Blood Pressure Service
│   └── 0x2A35 — Blood Pressure Measurement (indicate)
├── 0x1808 — Glucose Service
│   └── 0x2A18 — Glucose Measurement (notify)
├── 0x1822 — Pulse Oximeter Service
│   └── 0x2A5F — PLX Spot-Check Measurement
└── 0x1809 — Health Thermometer
    └── 0x2A1C — Temperature Measurement (indicate)

CONNECTION FLOW:
1. FlutterBluePlus.startScan(withServices: [serviceUUID])
2. Show scanResults list → user taps Connect
3. device.connect(autoConnect: false)
4. device.discoverServices()
5. Find characteristic by UUID
6. characteristic.setNotifyValue(true)
7. Listen to characteristic.onValueReceived
8. Parse bytes per GATT spec → call action callback

DATA PARSING:
├── Heart Rate (0x2A37):
│   ├── Byte 0: flags (bit 0 = HR format: 0=uint8, 1=uint16)
│   └── Bytes 1-2: heart rate value (BPM)
│
└── Blood Pressure (0x2A35):
    ├── Byte 0: flags
    ├── Bytes 1-2: systolic (IEEE-11073 SFLOAT)
    ├── Bytes 3-4: diastolic (SFLOAT)
    └── Bytes 5-6: mean arterial pressure (SFLOAT)

FIRESTORE STRUCTURE:
└── health_readings/{userId}/readings/{readingId}
    ├── deviceType: 'heart_rate' | 'blood_pressure'
    ├── value: { heartRate: 72 } or { systolic: 120, diastolic: 80 }
    ├── unit: 'bpm' | 'mmHg'
    ├── deviceName: 'Polar H10'
    └── timestamp: Timestamp

CONNECTION STATUS UI:
├── Green dot: connected, streaming
├── Yellow + spinner: reconnecting
└── Red dot: disconnected
```

## Common mistakes

- **Not implementing auto-reconnect logic, leaving users with a frozen screen when the device disconnects** — BLE health devices disconnect frequently — the blood pressure cuff turns off after a measurement, the heart rate monitor goes to sleep after 30 seconds of no movement, or the phone's OS kills the BLE connection to save battery. Without reconnect logic, the connection silently drops and no new data arrives. Fix: Listen to device.connectionState stream. On disconnect, update connection status UI immediately (red dot, message). Implement auto-reconnect: wait 3 seconds, attempt reconnect, retry up to 3 times. If all retries fail, show a clear manual reconnect prompt. Always show connection status prominently in the UI.
- **Using the short UUID form (0x180D) instead of the full GATT UUID when scanning or filtering** — FlutterBluePlus requires the full 128-bit UUID format: 0000180D-0000-1000-8000-00805F9B34FB. Using just 0x180D or 180D causes the scan to return no devices even if matching devices are nearby. Fix: Always use the full UUID format in scan filters and when comparing service/characteristic UUIDs: '0000180d-0000-1000-8000-00805f9b34fb' (lowercase for consistent comparison).
- **Requesting Bluetooth permissions after the user taps Connect instead of at app startup** — iOS and Android both require Bluetooth permission before any scan can begin. If permission is requested mid-flow (when the user taps Connect), the scan silently fails or crashes on iOS with no user-visible error. Fix: Request Bluetooth permissions at app launch in a Custom Action using the permission_handler package: await Permission.bluetooth.request() on Android, and include NSBluetoothAlwaysUsageDescription in Info.plist for iOS. Check permission status before initiating any scan.

## Best practices

- Request Bluetooth permissions at app startup with an explanation of why the app needs them, before the user reaches the device connection screen
- Always display connection status prominently — green dot for connected, red for disconnected, yellow for attempting to reconnect
- Filter BLE scans by service UUID using startScan(withServices: []) to find only relevant health devices and reduce battery drain from scanning all BLE devices
- Implement auto-reconnect with maximum 3 attempts and exponential backoff, then prompt for manual reconnect to avoid draining the user's battery with endless reconnect attempts
- Store each reading in Firestore with a timestamp so users can view historical trends and export their data
- Add threshold alerts: if a heart rate reading exceeds 150 BPM or blood pressure exceeds 140/90, trigger a local notification using flutter_local_notifications
- Test BLE connectivity on physical devices only — Bluetooth simulation in iOS simulator and Android emulator is unreliable

## Frequently asked questions

### How do I connect a Bluetooth health device to my FlutterFlow app?

Use the flutter_blue_plus package in a Custom Widget. Add it to your pubspec dependencies (Custom Code → Pubspec Dependencies). Create a widget that: (1) scans for BLE devices filtering by the health device's GATT service UUID, (2) displays found devices in a list, (3) connects to the selected device with device.connect(), (4) discovers services and finds the measurement characteristic, (5) calls setNotifyValue(true) to subscribe to real-time updates, and (6) parses incoming data bytes according to the GATT specification.

### What BLE UUID should I use for a heart rate monitor?

Heart rate monitors use the standardized Bluetooth GATT Heart Rate Profile. The service UUID is 0000180D-0000-1000-8000-00805F9B34FB (short form: 0x180D). The Heart Rate Measurement characteristic UUID is 00002A37-0000-1000-8000-00805F9B34FB (short form: 0x2A37). Filter scans by the service UUID to find only heart rate devices. The characteristic sends notifications — subscribe with setNotifyValue(true) and listen to onValueReceived for real-time BPM updates.

### How do I parse BLE heart rate data bytes in Flutter?

The GATT Heart Rate Measurement characteristic sends bytes in a defined format: Byte 0 is the flags byte. Check bit 0 of flags: if 0, the heart rate value is in byte 1 as a uint8 (use bytes[1]). If bit 0 is 1, the value is a uint16 across bytes 1-2 (use (bytes[2] << 8) + bytes[1]). For most consumer heart rate monitors, the value is uint8. Example: bytes [0x04, 0x48, 0x00] has flags=0x04, HR=0x48=72 BPM. Bytes [0x01, 0x60, 0x00] has flags=0x01 (uint16), HR = (0<<8)+0x60 = 96 BPM.

### Does the flutter_blue_plus package work on both iOS and Android?

Yes. flutter_blue_plus supports iOS and Android. iOS requires adding NSBluetoothAlwaysUsageDescription to Info.plist in the exported Xcode project (FlutterFlow can handle this via Settings → App Details → Permissions). Android requires BLUETOOTH, BLUETOOTH_ADMIN, and ACCESS_FINE_LOCATION permissions in AndroidManifest.xml. On Android 12+, also add BLUETOOTH_SCAN and BLUETOOTH_CONNECT permissions. Note: flutter_blue_plus does NOT work in the iOS Simulator or Android Emulator — test only on physical devices.

### What health devices are supported using GATT profiles?

Bluetooth GATT defines standardized profiles for: Heart Rate monitors (0x180D) — chest straps, fitness trackers. Blood Pressure monitors (0x1810) — arm cuffs. Glucose meters (0x1808) — diabetic glucose monitors. Pulse oximeters (0x1822) — fingertip SpO2 monitors. Body thermometers (0x1809). Weight scales (0x181D). Lung function spirometers (0x1846). Any device that implements the standard GATT profile for its category will work with the service and characteristic UUIDs defined in the Bluetooth SIG specification.

### What if I need help building a medical device integration for my FlutterFlow health app?

BLE health device integration involves parsing binary data formats, handling connection state machines, managing permissions across iOS and Android, and implementing reliable reconnect logic. RapidDev has built custom BLE integrations for health monitoring apps including heart rate monitors, blood pressure cuffs, and glucose meters. For medical-grade device integration with FDA or CE compliance considerations, professional development support is particularly important to get right.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-external-health-monitoring-devices-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-external-health-monitoring-devices-in-flutterflow
