WellAlly Logo
WellAlly康心伴
Development

Unifying Health Data: Integrating Apple HealthKit & Google Fit with React Native & Expo

A comprehensive guide to securely accessing and consolidating fitness data from Apple HealthKit and Google Fit into a single React Native Expo app. Covers permissions, custom hooks, and building a unified data model.

W
2025-12-12
11 min read

In the rapidly growing digital health space, providing users with a holistic view of their fitness and wellness data is no longer a feature but an expectation. For React Native developers, this presents a unique challenge: how to securely access and consolidate data from two distinct, platform-specific ecosystems – Apple's HealthKit on iOS and Google's Health Connect (the successor to Google Fit) on Android.

This tutorial will guide you through building a cross-platform health and fitness application using React Native and Expo. We'll harness the power of native modules to read key health metrics, demonstrating how to handle the intricacies of each platform while maintaining a clean, unified codebase. By the end, you'll have a working app that can fetch step counts, heart rate, and sleep data, and a robust framework for adding more metrics.

This matters to developers because mastering this integration opens the door to creating compelling health tech applications, from workout trackers to comprehensive wellness platforms, all from a single, manageable React Native project.

Prerequisites:

  • Node.js (LTS version) and npm/yarn installed.
  • Expo CLI installed globally.
  • A physical iOS device for testing Apple HealthKit and a physical Android device or emulator for Google Fit/Health Connect.
  • Basic understanding of React Native, TypeScript, and React Hooks.

Understanding the Problem: The Two Ecosystems

Integrating health data in a React Native app means dealing with two fundamentally different native APIs:

  • Apple HealthKit (iOS): A mature and deeply integrated framework on iOS. It acts as a central, encrypted repository for all health and fitness data. Access is granted on a per-data-type basis, and user privacy is paramount.
  • Health Connect (Android): Google's newer, unified platform that allows users to share data between their favorite health and fitness apps. It centralizes data that was previously often siloed within individual apps or accessible through the older Google Fit API.

The primary challenge is that these two platforms expose different data structures, use different data type identifiers, and have unique setup and permission flows. Our goal is to create an abstraction layer that hides this complexity, allowing our React components to request health data without needing to know which OS it's running on.


Prerequisites: Setting Up Your Expo Project

Because we need to use native code for both HealthKit and Health Connect, we cannot use the standard Expo Go app. We must create a custom development client. This client will be a version of your app that includes the necessary native modules.

Step 1: Initialize the Project

Let's start with a fresh Expo project using the TypeScript template:

code
npx create-expo-app MyHealthApp --template blank-typescript
cd MyHealthApp
Code collapsed

Step 2: Install Dependencies

We'll need several packages:

  • expo-dev-client: For building and using our custom client.
  • react-native-health: The bridge to Apple HealthKit.
  • react-native-health-connect: The bridge to Android's Health Connect.
  • expo-build-properties: To set the minimum SDK version for Android.

Install them all with one command:

code
npx expo install expo-dev-client react-native-health react-native-health-connect expo-build-properties
Code collapsed

Step 3: Configure the Project

We need to make several changes to our app.json file. This is where we'll configure permissions and plugins for our native projects.

Open your app.json and modify it to look like this:

code
{
  "expo": {
    "name": "MyHealthApp",
    "slug": "MyHealthApp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myhealthapp",
      "infoPlist": {
        "NSHealthShareUsageDescription": "We need access to your health data to show you your activity.",
        "NSHealthUpdateUsageDescription": "We need to write your workout data to Apple Health."
      }
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.yourcompany.myhealthapp",
      "permissions": [
        "android.permission.health.READ_STEPS",
        "android.permission.health.READ_HEART_RATE",
        "android.permission.health.READ_SLEEP"
      ]
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      "react-native-health",
      "expo-health-connect",
      [
        "expo-build-properties",
        {
          "android": {
            "compileSdkVersion": 34,
            "targetSdkVersion": 34,
            "minSdkVersion": 26
          }
        }
      ]
    ]
  }
}
Code collapsed

Key changes:

  • iOS infoPlist: We added descriptions for why we need HealthKit access. This is mandatory and will be shown to the user in the permission dialog.
  • Android permissions: We specified the Health Connect permissions our app will request.
  • plugins: We added the plugins for each library. The expo-health-connect plugin will automatically handle required modifications to the native Android project. The expo-build-properties plugin is used to ensure our Android app targets a recent enough SDK version for Health Connect.

Step 4: Prebuild and Run the Dev Client

Now, generate the native ios and android directories:

code
npx expo prebuild
Code collapsed

Finally, build and run your custom client on your devices:

For iOS (on a physical device):

code
npx expo run:ios
Code collapsed

For Android (on a physical device or emulator):

code
npx expo run:android
Code collapsed

This process might take a while the first time. Once it's running, you have a development environment ready to test our native health integrations.


The Core Logic: A Unified useHealthData Hook

To manage the platform differences, we'll create a custom hook, useHealthData. This hook will expose a simple API to our components, like requestPermissions() and fetchTodayStats(), and handle all the platform-specific logic internally.

Create a new directory src/hooks and a file inside it named useHealthData.ts.

Step 1: Defining a Common Data Structure

First, let's define a unified HealthData type. This ensures our application works with a consistent data shape, regardless of the source.

code
// src/hooks/useHealthData.ts

export interface HealthData {
  steps: number;
  heartRate: number;
  sleepHours: number;
}
Code collapsed

Step 2: Setting Up the Hook and Platform-Specific Modules

Now, let's create the basic structure for our hook and import the necessary libraries.

code
// src/hooks/useHealthData.ts
import { useState, useEffect } from 'react';
import { Platform } from 'react-native';
import AppleHealthKit, { HealthKitPermissions } from 'react-native-health';
import { initialize, requestPermission, readRecords } from 'react-native-health-connect';

export interface HealthData {
  steps: number;
  heartRate: number;
  sleepHours: number;
}

// Define permissions for Apple HealthKit
const healthKitPermissions: HealthKitPermissions = {
  permissions: {
    read: [
      AppleHealthKit.Constants.Permissions.Steps,
      AppleHealthKit.Constants.Permissions.HeartRate,
      AppleHealthKit.Constants.Permissions.SleepAnalysis,
    ],
    write: [],
  },
};

// Define permissions for Google Health Connect
const healthConnectPermissions = [
  { accessType: 'read', recordType: 'Steps' },
  { accessType: 'read', recordType: 'HeartRate' },
  { accessType: 'read', recordType: 'SleepSession' },
];

export const useHealthData = () => {
  const [healthData, setHealthData] = useState<HealthData | null>(null);
  const [isInitialized, setIsInitialized] = useState(false);

  // ... functions will go here
};
Code collapsed

Step 3: Implementing Permissions

Let's implement the requestPermissions function. This will be the first thing we call to get the user's consent.

code
// Inside useHealthData hook

const requestPermissions = async (): Promise<boolean> => {
  if (Platform.OS === 'ios') {
    return new Promise((resolve) => {
      AppleHealthKit.initHealthKit(healthKitPermissions, (error, results) => {
        if (error) {
          console.error("Error initializing HealthKit:", error);
          return resolve(false);
        }
        setIsInitialized(true);
        resolve(true);
      });
    });
  } else if (Platform.OS === 'android') {
    try {
      const isInitialized = await initialize();
      if (!isInitialized) {
        console.error('Failed to initialize Health Connect');
        return false;
      }
      const grantedPermissions = await requestPermission(healthConnectPermissions);
      console.log('Granted Permissions:', grantedPermissions);
      setIsInitialized(true);
      return true;
    } catch (error) {
      console.error("Error initializing Health Connect:", error);
      return false;
    }
  }
  return false;
};
Code collapsed

How it works:

  • iOS: We call AppleHealthKit.initHealthKit. This function both initializes the connection and presents the standard iOS permissions modal to the user. The user can grant or deny each permission individually.
  • Android: The process is a two-step. First, initialize() checks if Health Connect is available and ready. Then, requestPermission() opens the Health Connect app's permission screen.

Step 4: Fetching and Normalizing Data

This is where we unify the data. We'll create a fetchTodayStats function that calls the appropriate native method and transforms the result into our HealthData interface.

code
// Inside useHealthData hook

const fetchTodayStats = async () => {
  if (!isInitialized) {
    console.warn("Health data not initialized. Please request permissions first.");
    return;
  }

  const today = new Date();
  const startDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0).toISOString();
  const endDate = today.toISOString();

  let steps = 0;
  let heartRate = 0;
  let sleepHours = 0;

  if (Platform.OS === 'ios') {
    // --- iOS Data Fetching ---
    const options = { date: endDate };

    const stepResult = await new Promise<number>((resolve) => {
      AppleHealthKit.getStepCount(options, (err, result) => {
        resolve(result?.value || 0);
      });
    });
    steps = stepResult;
    
    // ... iOS Heart Rate and Sleep fetching to be added

  } else if (Platform.OS === 'android') {
    // --- Android Data Fetching ---
    const stepRecords = await readRecords('Steps', {
      timeRangeFilter: {
        operator: 'between',
        startTime: startDate,
        endTime: endDate,
      },
    });
    steps = stepRecords.reduce((total, record) => total + (record as any).count, 0);

    // ... Android Heart Rate and Sleep fetching to be added
  }

  setHealthData({ steps, heartRate, sleepHours });
};
Code collapsed

This snippet only shows fetching steps to illustrate the core concept. The full implementation would involve fetching HeartRate and SleepAnalysis/SleepSession records and normalizing them (e.g., averaging heart rate, summing sleep duration).


Putting It All Together: The UI

Now we can use our useHealthData hook in a React component to display the data.

Create a file App.tsx and add the following code:

code
// App.tsx
import React, { useEffect } from 'react';
import { View, Text, Button, StyleSheet, SafeAreaView } from 'react-native';
import { useHealthData, HealthData } from './src/hooks/useHealthData'; // Assuming the hook is completed

export default function App() {
  const { healthData, requestPermissions, fetchTodayStats } = useHealthData();

  useEffect(() => {
    // Automatically request permissions on app start
    requestPermissions().then(success => {
      if (success) {
        fetchTodayStats();
      }
    });
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>My Health Stats</Text>
      <View style={styles.card}>
        <Stat label="Steps" value={healthData?.steps ?? 'N/A'} />
        <Stat label="Heart Rate (bpm)" value={healthData?.heartRate ?? 'N/A'} />
        <Stat label="Sleep (hours)" value={healthData?.sleepHours ?? 'N/A'} />
      </View>
      <Button title="Refresh Data" onPress={fetchTodayStats} />
    </SafeAreaView>
  );
}

const Stat = ({ label, value }: { label: string; value: string | number }) => (
  <View style={styles.stat}>
    <Text style={styles.label}>{label}</Text>
    <Text style={styles.value}>{value}</Text>
  </View>
);

const styles = StyleSheet.create({
  // ... Add styles for container, title, card, etc.
});

Code collapsed

This component provides a simple UI to request permissions and display the fetched data, demonstrating the power of our abstraction.


Security Best Practices

When handling sensitive health data, security and user privacy are non-negotiable.

  • Request Only What You Need: Only ask for read/write permissions for the specific data types your app's features require.
  • Be Transparent: Clearly explain in your UI and privacy policy why you need access to health data. The NSHealthShareUsageDescription in app.json is your first opportunity to build trust.
  • Handle Data Responsibly: Do not store health data on your servers unless absolutely necessary and compliant with regulations like HIPAA or GDPR. Process data on the device whenever possible.
  • Graceful Degradation: Your app should function gracefully if the user denies permissions. Don't crash or lock features unnecessarily; instead, provide a clear explanation of what's unavailable and how they can grant permissions later from the system settings.

Conclusion

Integrating Apple HealthKit and Google Fit in a React Native Expo app is a powerful way to build engaging health and wellness applications. By using a custom development client and creating a unified data hook, you can effectively manage platform-specific APIs while keeping your application logic clean and maintainable. This approach provides a solid foundation that you can extend to support a wide array of health metrics, from nutrition to workout tracking.

Next Steps

  • Complete the fetchTodayStats function to include heart rate and sleep data normalization.
  • Implement write functionality to save workouts or other health data back to HealthKit and Health Connect.
  • Explore background data fetching to keep your app's data fresh.
  • Build more sophisticated UI components to visualize health data with charts and graphs.

Resources

#

Article Tags

reactnativeexpohealthtechmobile
W

WellAlly's core development team, comprised of healthcare professionals, software engineers, and UX designers committed to revolutionizing digital health management.

Expertise

Healthcare TechnologySoftware DevelopmentUser ExperienceAI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey

© 2024 康心伴 WellAlly · Professional Health Management