WellAlly Logo
WellAlly康心伴
Development

Unifying Apple HealthKit & Google Fit in a React Native App

A complete guide to integrating Apple HealthKit and Google Fit (Health Connect) in React Native. Learn to build a unified API to securely request permissions, read steps/heart rate, and write workout data.

W
2025-12-19
12 min read

In the rapidly growing health tech space, users expect their apps to seamlessly connect with their primary health ecosystems—Apple Health and Google Fit. For React Native developers, this presents a unique challenge: how do you create a single, elegant solution for two very different native platforms? This tutorial will guide you through building a robust, unified API to read and write user health data securely.

We will build a React Native application that can:

  • Request user permission to access health data.
  • Read daily step counts and recent heart rate samples.
  • Write a completed workout session to the native health store.
  • Abstract platform-specific logic into a unified service.

This guide is for developers with a basic understanding of React Native. We will be working with a bare React Native setup to have full control over the native project configurations.

Why this matters to developers:

  • Enhanced User Experience: Integrating with the OS-level health store provides a seamless experience for users, who can see all their health data in one place.
  • Data-Rich Applications: Leverage valuable health data to provide personalized insights, coaching, and features.
  • Increased App Stickiness: Apps that become part of a user's daily health routine are more likely to be retained.

Understanding the Problem

Apple HealthKit (iOS) and Google Fit's new replacement, Health Connect (Android), are powerful but fundamentally different APIs. A naive implementation would lead to tangled, platform-specific code littered throughout your app.

  • Platform Divergence: On iOS, we use react-native-health, a mature library for HealthKit. On Android, the landscape has shifted from the Google Fit API to the more modern and privacy-centric Health Connect API, for which we'll use react-native-health-connect.
  • Permission Models: Each platform has its own specific workflow for requesting permissions, which must be handled gracefully.
  • Data Structures: The way data is structured and queried is different between HealthKit and Health Connect.

Our approach is to create a "Health Service" abstraction layer. This layer will expose a set of simple, unified methods (e.g., getStepCount(), saveWorkout()) to the rest of our application. Internally, it will detect the operating system and call the appropriate native library.

Prerequisites

  • Node.js and Watchman: Ensure you have Node.js (LTS version) and Watchman installed.
  • React Native Environment: A properly configured development environment for both iOS and Android (Xcode and Android Studio). Follow the official React Native environment setup guide.
  • Physical Devices: Access to a physical iPhone and an Android device is highly recommended. Simulators have limited or no health data.
  • React Native Version: 0.71 or higher is recommended.

Step 1: Project Setup and Library Installation

First, let's create a new React Native project.

code
npx react-native@latest init HealthApp
cd HealthApp
Code collapsed

Now, we'll install the necessary libraries for each platform.

code
# For Apple HealthKit on iOS
npm install react-native-health

# For Health Connect on Android
npm install react-native-health-connect
Code collapsed

For iOS, we also need to install the pods.

code
cd ios && pod install && cd ..
Code collapsed

Step 2: iOS Native Configuration (react-native-health)

What we're doing

We need to configure the native iOS project to enable HealthKit and let the user know why we're requesting access to their health data.

Implementation

  1. Enable HealthKit in Xcode:

    • Open /ios/HealthApp.xcworkspace in Xcode.
    • Select the HealthApp project in the navigator, then select the HealthApp target.
    • Go to the "Signing & Capabilities" tab.
    • Click + Capability and double-click HealthKit. This will add the capability and create a .entitlements file.
  2. Add Usage Descriptions to Info.plist:

    • In Xcode, open the Info.plist file.
    • Add two new keys by clicking the + icon:
      • Privacy - Health Share Usage Description: Set the value to a user-friendly message, e.g., "We need to read your health data to provide personalized insights."
      • Privacy - Health Update Usage Description: Set the value to "We need to write workout data to track your activity in Apple Health."

How it works

Enabling the HealthKit capability tells iOS that our app intends to interact with the HealthKit store. The Info.plist entries are crucial for user privacy; iOS will display these strings to the user when our app first requests permission. Without these, the app will crash.

Step 3: Android Native Configuration (react-native-health-connect)

What we're doing

For Android, we need to declare our app's intent to use Health Connect and specify the exact permissions we'll be requesting in the AndroidManifest.xml.

Implementation

  1. Configure AndroidManifest.xml:

    • Open /android/app/src/main/AndroidManifest.xml.
    • Add the necessary permissions inside the <manifest> tag.
    code
    <!-- Required to specify which Health Connect permissions the app can request -->
    <uses-permission android:name="android.permission.health.READ_STEPS" />
    <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
    <uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
    <uses-permission android:name="android.permission.health.READ_EXERCISE" />
    
    Code collapsed
    • Inside the <application> tag, add an intent filter to handle the Health Connect permission rationale screen.
    code
    <activity
        android:name=".MainActivity"
        android:exported="true">
        <!-- ... existing intent-filter ... -->
        <intent-filter>
            <action android:name="android.health.connect.action.ACTION_HEALTH_CONNECT_SETTINGS" />
        </intent-filter>
    </activity>
    
    Code collapsed

How it works

The <uses-permission> tags inform the Android OS and the Google Play Store about the sensitive data your app might access. The intent-filter allows your app to correctly deep-link to the Health Connect permission management screen, which is a required part of the user flow.

Step 4: Creating a Unified Health API Service

This is where we abstract the platform differences.

What we're doing

We will create a single JavaScript file, src/services/HealthService.js, that will expose a consistent set of functions to our React components. This service will internally call the appropriate library based on Platform.OS.

Implementation

Create a new directory src/services and a file HealthService.js.

code
// src/services/HealthService.js
import { Platform } from 'react-native';
import AppleHealthKit from 'react-native-health';
import {
  initialize,
  requestPermission,
  readRecords,
  writeRecords,
} from 'react-native-health-connect';

// Define permissions for both platforms
const permissions = {
  ios: {
    permissions: {
      read: [
        AppleHealthKit.Constants.Permissions.Steps,
        AppleHealthKit.Constants.Permissions.HeartRate,
        AppleHealthKit.Constants.Permissions.Workout,
      ],
      write: [AppleHealthKit.Constants.Permissions.Workout],
    },
  },
  android: {
    permissions: [
      { accessType: 'read', recordType: 'Steps' },
      { accessType: 'read', recordType: 'HeartRate' },
      { accessType: 'write', recordType: 'ExerciseSession' },
      { accessType: 'read', recordType: 'ExerciseSession' },
    ],
  },
};

const requestPermissions = async () => {
  if (Platform.OS === 'ios') {
    return new Promise((resolve, reject) => {
      AppleHealthKit.initHealthKit(permissions.ios, (error, results) => {
        if (error) {
          console.log('[ERROR] Failed to grant permissions!', error);
          return reject(error);
        }
        resolve(results);
      });
    });
  } else {
    try {
      await initialize();
      const granted = await requestPermission(permissions.android);
      return granted;
    } catch (error) {
      console.log('[ERROR] Failed to grant permissions!', error);
      throw error;
    }
  }
};

const getDailyStepCount = async () => {
  const today = new Date();
  if (Platform.OS === 'ios') {
    return new Promise((resolve, reject) => {
      const options = { date: today.toISOString() };
      AppleHealthKit.getStepCount(options, (err, result) => {
        if (err) {
          return reject(err);
        }
        resolve(result.value);
      });
    });
  } else {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const records = await readRecords('Steps', {
        timeRangeFilter: {
          operator: 'between',
          startTime: today.toISOString(),
          endTime: new Date().toISOString(),
        },
      });
      return records.reduce((sum, record) => sum + record.count, 0);
  }
};


// Other methods (getHeartRate, saveWorkout) will go here...

export default {
  requestPermissions,
  getDailyStepCount,
  // ... export other methods
};
Code collapsed

How it works

The requestPermissions function uses Platform.OS to decide whether to call AppleHealthKit.initHealthKit or react-native-health-connect's requestPermission. We've defined the permissions for both platforms in a structured way. The getDailyStepCount function does the same, adapting the options and response handling for each library.

Step 5: Reading & Writing Data

Let's complete our HealthService.js with functions for heart rate and workouts.

What we're doing

We'll add getHeartRateSamples and saveWorkout to our service, again handling platform differences.

Implementation

Add the following functions to src/services/HealthService.js.

code
// ... inside src/services/HealthService.js

const getHeartRateSamples = async () => {
  const now = new Date();
  const yesterday = new Date();
  yesterday.setDate(now.getDate() - 1);

  if (Platform.OS === 'ios') {
    return new Promise((resolve, reject) => {
      const options = {
        startDate: yesterday.toISOString(),
        endDate: now.toISOString(),
      };
      AppleHealthKit.getHeartRateSamples(options, (err, results) => {
        if (err) {
          return reject(err);
        }
        resolve(results);
      });
    });
  } else {
    const records = await readRecords('HeartRate', {
      timeRangeFilter: {
        operator: 'between',
        startTime: yesterday.toISOString(),
        endTime: now.toISOString(),
      },
    });
    return records.map(record => ({
      value: record.samples[0].beatsPerMinute,
      endDate: record.time,
    }));
  }
};

const saveWorkout = async () => {
  const startTime = new Date();
  startTime.setHours(startTime.getHours() - 1); // 1-hour workout
  const endTime = new Date();

  if (Platform.OS === 'ios') {
    return new Promise((resolve, reject) => {
      const options = {
        type: AppleHealthKit.Constants.Activities.Running,
        startDate: startTime.toISOString(),
        endDate: endTime.toISOString(),
        energyBurned: 300, // in calories
        totalDistance: 5, // in kilometers
      };
      AppleHealthKit.saveWorkout(options, (err, result) => {
        if (err) {
          return reject(err);
        }
        resolve(result);
      });
    });
  } else {
    const records = await writeRecords([
      {
        recordType: 'ExerciseSession',
        startTime: startTime.toISOString(),
        endTime: endTime.toISOString(),
        exerciseType: 'Running',
        title: 'Morning Run',
      },
    ]);
    return records;
  }
};

export default {
  requestPermissions,
  getDailyStepCount,
  getHeartRateSamples,
  saveWorkout,
};
Code collapsed

Putting It All Together

Now let's use our shiny new service in a React component.

Implementation

Replace your App.tsx (or App.js) content with the following:

code
// App.tsx
import React, { useState, useEffect } from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  Button,
  View,
} from 'react-native';
import HealthService from './src/services/HealthService';

const App = () => {
  const [hasPermissions, setHasPermissions] = useState(false);
  const [steps, setSteps] = useState(0);
  const [heartRate, setHeartRate] = useState(0);

  useEffect(() => {
    // Check for permissions on component mount
    HealthService.requestPermissions().then(setHasPermissions).catch(console.error);
  }, []);

  const fetchHealthData = () => {
    if (!hasPermissions) {
      alert('Permissions not granted!');
      return;
    }

    HealthService.getDailyStepCount()
      .then(setSteps)
      .catch(err => console.error('Steps Error:', err));

    HealthService.getHeartRateSamples()
      .then(samples => {
        if (samples.length > 0) {
          setHeartRate(samples[samples.length - 1].value);
        }
      })
      .catch(err => console.error('Heart Rate Error:', err));
  };

  const handleSaveWorkout = () => {
    HealthService.saveWorkout()
      .then(() => alert('Workout saved successfully!'))
      .catch(err => console.error('Workout Save Error:', err));
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle={'dark-content'} />
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View style={styles.section}>
          <Text style={styles.title}>React Native Health Demo</Text>
          <Text>Permissions Granted: {hasPermissions ? '✅' : '❌'}</Text>
        </View>
        <View style={styles.section}>
          <Button title="Fetch Health Data" onPress={fetchHealthData} />
          <Text style={styles.dataText}>Steps Today: {steps}</Text>
          <Text style={styles.dataText}>Latest Heart Rate: {heartRate} BPM</Text>
        </View>
        <View style={styles.section}>
          <Button title="Save a Test Workout" onPress={handleSaveWorkout} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1 },
  section: {
    padding: 24,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
    marginBottom: 8,
  },
  dataText: {
    fontSize: 18,
    marginTop: 8,
  },
});

export default App;
Code collapsed

Now, run your app on a physical device.

code
npx react-native run-ios --device="Your iPhone Name"
npx react-native run-android --deviceId="Your Device ID"
Code collapsed

You should be prompted for health permissions. After granting them, you can fetch data and save a test workout!

Security Best Practices

Working with health data comes with great responsibility.

  • Request Minimal Scope: Only ask for the permissions you absolutely need for your app's functionality. Users are wary of apps that ask for everything.
  • Explain Why: Before showing the permission prompt, use a modal or a screen to explain to the user why you need their data and what you will do with it.
  • Data Privacy: Never send raw HealthKit or Health Connect data to your servers without explicit user consent and a clear privacy policy. Anonymize or aggregate data whenever possible.
  • Handle Denials Gracefully: Users can deny permissions. Your app should not crash. Instead, display a message explaining that certain features are unavailable and provide a shortcut to the settings to enable permissions later.
  • HIPAA: If your app is considered to be used in a clinical context in the US, you must be aware of HIPAA (Health Insurance Portability and Accountability Act) regulations, which govern the privacy and security of protected health information.

Conclusion

You have successfully built a cross-platform React Native app that integrates with both Apple HealthKit and Google's Health Connect. By creating a unified API service, you've kept your application logic clean and maintainable, isolating the platform-specific code in one place. This architecture makes it easy to add support for new health metrics in the future.

The next step is to explore more data types, create beautiful visualizations with this data, and build features that provide real value to your users on their health journey.

Resources

#

Article Tags

reactnative
mobile
api
healthtech
W

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

Expertise

Healthcare Technology
Software Development
User Experience
AI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey