Back in March, when I shipped my first package, I made myself a promise: every time I solve a native-bridge problem twice, it becomes a package. This is the second installment — and this time the bridge problem was blocking my own startup.
The blocker
I'm building Kalma — a place where your health data actually belongs to you, especially if your medical history is scattered across countries and providers the way it is for nomads and expats. For that to work, Kalma has to read the data your phone already collects: steps, sleep, heart rate, workouts.
On iOS that means HealthKit. On Android it now means Health Connect. And that "now" is the problem — most of the libraries I evaluated were built for a world before Health Connect, wrapped only one platform, or hadn't kept pace with the New Architecture. I kept hitting walls, and the feature kept slipping.
So I did the thing I said I'd do: built the bridge properly, once, in the open.
One API, both platforms
pnpm add @mbdayo/react-native-health-kits
import HealthKits from "@mbdayo/react-native-health-kits";
if (await HealthKits.isAvailable()) {
await HealthKits.requestPermissions(["steps", "sleep", "heartRate"]);
const steps = await HealthKits.readData("steps", { aggregate: true });
}
Under the hood it's Turbo Modules wrapping HealthKit on iOS and
Health Connect on Android, with one surface for both: reading, writing,
real-time subscriptions, and 25+ data types — from heart rate and sleep
to nutrition, blood pressure, and hydration.
What two platforms teach you about one idea
The interesting part wasn't the bridging; it was the philosophy gap. iOS and Android have fundamentally different opinions about the same concept:
- Permissions: HealthKit wants usage strings in
Info.plistand asks per data type; Health Connect wants manifest entries and a registered permission activity. Same idea, two ceremonies. - Availability: HealthKit needs a physical device; Health Connect
is built into Android 14 but a separate install on Android 9–13.
isAvailable()exists because "it depends" needed to be one call. - Aggregation: the platforms only agree on summing a handful of types — steps, distance, calories, floors, hydration. Pretend otherwise in the API and you'd be lying to someone. The README says so out loud instead.
Designing the unified surface meant choosing honesty over symmetry wherever the platforms disagree. That's the real work of a bridge library — the native calls are the easy part.
The streak continues
Kalma's health-data feature shipped on this package, which is the only
benchmark that matters: it's extracted from production need, not
imagined API design. Two packages into the @mbdayo scope, the promise
is holding.
If you're pulling health data into a React Native app, give it a try — and if something's off, you know where the issues tab is.