How I built a unified, local-first bridge for Apple HealthKit and Google Health Connect in Expo

by•

Hey everyone! 👋

If you’ve ever tried to build a health, fitness, or utility feature in a cross-platform React Native app, you know the pain: you aren't writing one feature-set, you're fighting two entirely separate, heavy native ecosystems.

1. Beginning

On iOS, you have the mature Apple HealthKit API. On Android, you have Health Connect, Google’s unified API framework that replaced the legacy Google Fit SDK. When I was building the hydration tracking architecture for my new app, WaterPaw, I wanted a completely modular solution.

I didn't want platform conditional checks (`Platform.OS === 'ios'`) messy up my UI layer. I wanted a single, abstract custom React hook that handled availability, permissions, and data fetching under one hood. Here is the clean design pattern I used to unify them, utilizing Expo Config Plugins to handle the native configurations automatically.

First, I defined a concrete blueprint (`interface`) so the frontend doesn't care which native platform it's running on:

export type HydrationData = {
  value: number; // in milliliters
  date: string;
};

export interface HealthBridge {
  isAvailable(): Promise<boolean>;
  requestPermissions(): Promise<boolean>;
  getHydrationData(date: Date): Promise<HydrationData[]>;
}

2. Concrete Bridges

Then, I separated the exact implementation details into isolated native wrappers.

  • iOS Wrapper: Powered by react-native-health to check initialized status and pass standard HealthKit permission matrices.

  • Android Wrapper: Powered by react-native-health-connect using the required two-step execution flow (initialization check followed by a separate permission intent contract).

3. The Hook: Dynamic Platform Export

The magic happens when exporting the bridge dynamically based on the platform, allowing the components to call a singular custom hook:

import { Platform } from 'react-native';
import { iOSHealthBridge } from './iOSHealthBridge';
import { AndroidHealthBridge } from './AndroidHealthBridge';

const HealthAPI = Platform.OS === 'ios' ? iOSHealthBridge : AndroidHealthBridge;

export function useHydrationSync() {
  const syncData = async () => {
    const available = await HealthAPI.isAvailable();
    if (!available) return;

    const hasPermission = await HealthAPI.requestPermissions();
    if (hasPermission) {
      const records = await HealthAPI.getHydrationData(new Date());
      // Commit directly to a local-first store (e.g. AsyncStorage or Zustand)
    }
  };

  return { syncData };
}

4. The Big Expo Gotcha 💡

Because both native packages utilize heavily customized native code (Objective-C and Kotlin), they will not compile inside standard Expo Go.

To bypass this cleanly without manually messing around inside Xcode or Android Studio, you must declare config plugins inside your app.json. Note that Health Connect explicitly requires a minimum SDK layout of 26:

{
  "expo": {
    "plugins": [
      ["react-native-health", { "NSHealthShareUsageDescription": "Sync your hydration records." }],
      ["expo-health-connect"],
      [
        "expo-build-properties",
        { "android": { "minSdkVersion": 26 } }
      ]
    ]
  }
}

After updating this, running a quick npx expo prebuild --clean handles the native generation automatically.

Why go through all this trouble?

When building minimalist utilities, performance and zero friction are everything. Using this architecture, the UI stays snappy at 60 FPS, updates are instantaneous, and because it is local-first, the app respects the user's data privacy completely. No third-party servers, no mandatory account sign-ups.

I used this structural logic to power the smart reminders and mascot progression systems inside WaterPaw, which I just launched!

I'd love to hear from other mobile devs here: How are you handling cross-platform data syncing in 2026? Are you sticking to native bridges, or moving towards pure Kotlin Multiplatform / Flutter wrappers?

8 views

Add a comment

Replies

Be the first to comment