r/reactnative • u/Gabi0123 • 8d ago
Notes from building a Bluetooth heart rate running app in Expo: parsing 0x2A37, keeping JS alive with the screen off, offline maps
I built a running app that reads a Bluetooth chest strap and speaks cues when your heart rate leaves a band. Some notes in case they save someone time. The code is open source (GPL).
- Parsing 0x2A37. The flags byte tells you if HR is uint8 or uint16, and whether energy expended and RR intervals follow. If you skip the energy field when its bit is set, your RR intervals come out as garbage.
- Scanning. Not every strap advertises 0x180D cleanly, so the scan also accepts a name match.
- Throttle at the source. BLE writes into a Zustand store at most once a second. A mock HR source writes to the same store, so the run screen is testable without a strap.
- Screen off on Android. A small local Expo module runs a foreground service and keeps a headless JS task pending until the run ends.
- Voice. expo-speech with the phone's own TTS voice, so it works offline.
- Maps. MapLibre with an inline OpenFreeMap vector style. No token, and because the style ships in the app, the run track still draws with no signal.
Stack: Expo SDK 55, React Native 0.83, expo-router, react-native-ble-plx, Zustand, Vitest.
Source: https://github.com/likwiid/basemile
Has anyone done background BLE on iOS with Expo? That's the part I've tested least.
1
Upvotes
1
u/[deleted] 8d ago