WellAlly Logo
WellAlly康心伴
Development

Syncing Apple HealthKit in React Native: A Developer's Guide

Learn how to integrate Apple HealthKit into your React Native app. This step-by-step guide covers Xcode setup, permissions, and reading/writing health data like steps and workouts using custom hooks.

W
2025-12-11
9 min read

In the booming world of health and fitness technology, users expect seamless integration between their favorite apps and their central health dashboard—Apple Health. For React Native developers, accessing this rich, user-permissioned data can seem daunting. It often involves navigating native iOS configurations, handling sensitive permissions, and understanding a completely different ecosystem.

This article demystifies the process. We'll build a practical, step-by-step guide to integrating Apple's HealthKit into a React Native application. We will use the popular react-native-health library to read and write fundamental health metrics like step count, sleep analysis, and workout data. To make our code clean, reusable, and easy to manage, we'll wrap all our HealthKit interactions in a custom React hook.

Prerequisites:

  • A macOS computer with Xcode installed.
  • A physical iOS device (HealthKit features are limited on simulators).
  • Node.js (LTS version) and a React Native development environment.
  • Basic knowledge of React Native and TypeScript.

Why this matters to developers: Integrating with Apple Health can significantly boost user engagement by providing a more holistic and personalized experience. It allows your app to contribute to and benefit from the user's comprehensive health data, making it more valuable and competitive.

Understanding the Problem

Apple HealthKit is a powerful but protected framework. It acts as a central repository for a user's health and fitness data. Apps cannot access this data without explicit, granular user permission. The main challenges for a React Native developer are:

  1. Native Configuration: HealthKit is an iOS-native framework. It requires specific configurations in Xcode that are outside the usual JavaScript environment.
  2. Permissions Management: Apple has a strict privacy model. If a user denies permission once, they won't be prompted again. They must manually enable it in the Settings app, a flow your app needs to handle.
  3. Data Synchronization: Reading and writing data involves understanding various data types, units, and asynchronous operations.
  4. UI/UX: The user interface must clearly explain why these permissions are needed and gracefully handle cases where access is denied.

Our approach is to tackle these challenges head-on using a library to abstract away the native complexity and a custom hook to create a clean, reusable interface for our React Native components.

Prerequisites

Before we start coding, let's set up our project.

  1. Create a new React Native project (with TypeScript):

    code
    npx create-react-native-app MyHealthApp --template react-native-template-typescript
    cd MyHealthApp
    
    Code collapsed
  2. Install the HealthKit library: We'll use react-native-health, a widely used library for HealthKit integration.

    code
    npm install react-native-health
    
    Code collapsed
  3. Install iOS dependencies:

    code
    cd ios && pod install
    
    Code collapsed

Step 1: Native iOS Configuration ✨

This is the most crucial step. If you get this wrong, nothing else will work.

What we're doing

We need to tell Xcode that our app will use the HealthKit framework and provide messages explaining to the user why we need access to their health data.

Implementation

  1. Open the Xcode project: Open the ios/MyHealthApp.xcworkspace file in Xcode.

  2. Add the HealthKit Capability:

    • Select your project in the left-hand navigator.
    • Go to the "Signing & Capabilities" tab.
    • Click "+ Capability".
    • Search for "HealthKit" and double-click to add it. This tells iOS your app intends to use the HealthKit framework.
  3. Configure Info.plist for Permissions:

    • In Xcode, find and open the Info.plist file.
    • Add two new keys by clicking the "+" button:
      • Privacy - Health Share Usage Description: This message is shown when you request permission to read data.
      • Privacy - Health Update Usage Description: This message is shown when you request permission to write data.
    • Provide user-friendly string values for each, for example:
      • Value for Share Usage: Our app uses your health data to provide personalized insights and track your progress.
      • Value for Update Usage: Our app needs to save your workouts and activity data to Apple Health.

    Your Info.plist source code should look like this:

    code
    <key>NSHealthShareUsageDescription</key>
    <string>Our app uses your health data to provide personalized insights and track your progress.</string>
    <key>NSHealthUpdateUsageDescription</key>
    <string>Our app needs to save your workouts and activity data to Apple Health.</string>
    
    Code collapsed

How it works

Adding the HealthKit capability links the necessary native frameworks to your project. The Info.plist entries are a mandatory security feature from Apple. iOS will automatically show these messages on the permission consent screen, so it's essential they are clear and build trust with your users.

Step 2: Creating a Custom useHealthData Hook

To keep our component code clean, we'll create a custom hook called useHealthData.ts. This hook will manage permissions and all interactions with the react-native-health library.

What we're doing

We'll define the specific read and write permissions our app needs and create a function to initialize HealthKit.

Implementation

  1. Create the hook file: In your project's src directory, create a new folder hooks and a file inside it named useHealthData.ts.

  2. Set up the initial hook structure:

    code
    // src/hooks/useHealthData.ts
    import AppleHealthKit, {
      HealthKitPermissions,
    } from 'react-native-health';
    import { useEffect, useState } from 'react';
    
    /* Define the permissions we want to request */
    const permissions: HealthKitPermissions = {
      permissions: {
        read: [
          AppleHealthKit.Constants.Permissions.Steps,
          AppleHealthKit.Constants.Permissions.SleepAnalysis,
          AppleHealthKit.Constants.Permissions.Workout,
        ],
        write: [
          AppleHealthKit.Constants.Permissions.Steps,
          AppleHealthKit.Constants.Permissions.SleepAnalysis,
          AppleHealthKit.Constants.Permissions.Workout,
        ],
      },
    };
    
    const useHealthData = () => {
      const [hasPermissions, setHasPermissions] = useState(false);
    
      useEffect(() => {
        // Initialize HealthKit
        AppleHealthKit.initHealthKit(permissions, (error: string) => {
          if (error) {
            console.log('[ERROR] Cannot grant permissions!');
            return;
          }
          setHasPermissions(true);
        });
      }, []);
    
      // We'll add functions to read/write data here
    
      return {
        hasPermissions,
      };
    };
    
    export default useHealthData;
    
    Code collapsed

How it works

We import AppleHealthKit and HealthKitPermissions from the library. We then define a permissions object that specifies exactly which data types we want to read and write. The useEffect hook calls AppleHealthKit.initHealthKit, which triggers the native iOS permission prompt the first time it's called. If the user grants permission, our hasPermissions state is set to true.

Step 3: Reading Health Data (Steps, Sleep, Workouts)

Now let's extend our hook to read data.

What we're doing

We'll add functions to our useHealthData hook to fetch the user's step count, sleep data, and workout history for the last 7 days.

Implementation

code
// Add these inside the useHealthData hook, before the return statement

const [steps, setSteps] = useState(0);
const [sleep, setSleep] = useState<any[]>([]); // Simplified for example
const [workouts, setWorkouts] = useState<any[]>([]); // Simplified for example

useEffect(() => {
  if (!hasPermissions) {
    return;
  }

  const options = {
    startDate: new Date(new Date().setDate(new Date().getDate() - 7)).toISOString(), // Last 7 days
    endDate: new Date().toISOString(),
  };

  // Fetch Step Count
  AppleHealthKit.getStepCount(options, (err, results) => {
    if (err) {
      console.log('Error getting step count:', err);
      return;
    }
    setSteps(results.value);
  });

  // Fetch Sleep Analysis
  AppleHealthKit.getSleepSamples(options, (err, results) => {
    if (err) {
      console.log('Error getting sleep samples:', err);
      return;
    }
    setSleep(results);
  });

  // Fetch Workouts
  AppleHealthKit.getSamples({ ...options, type: 'Workout' }, (err, results) => {
    if (err) {
        console.log('Error getting workout samples:', err);
        return;
    }
    setWorkouts(results);
  });

}, [hasPermissions]);

// Update the return statement
return {
  hasPermissions,
  steps,
  sleep,
  workouts,
};
Code collapsed

How it works

We use a second useEffect that runs whenever hasPermissions changes to true. Inside, we define date options to query data from the last week. We then call the specific methods provided by react-native-health like getStepCount and getSleepSamples. Each function takes the date options and a callback, which returns either an error or the requested data.

Step 4: Writing Health Data (Saving a Workout)

Reading data is useful, but many apps also need to contribute data back to HealthKit.

What we're doing

We'll add a function to our hook that allows us to save a new workout to Apple Health.

Implementation

code
// Add this function inside the useHealthData hook
const saveWorkout = () => {
  if (!hasPermissions) {
    console.log('Cannot save workout without permissions.');
    return;
  }

  const options = {
    type: AppleHealthKit.Constants.Workouts.Running,
    startDate: new Date(new Date().getTime() - 60 * 60 * 1000).toISOString(), // 1 hour ago
    endDate: new Date().toISOString(),
    energyBurned: 350, // in calories
    totalDistance: 5, // in kilometers
  };

  AppleHealthKit.saveWorkout(options, (err, results) => {
    if (err) {
      console.log('Error saving workout:', err);
      return;
    }
    console.log('Workout saved successfully:', results);
    // You might want to refetch workouts here to update the UI
  });
};

// Update the return statement
return {
  hasPermissions,
  steps,
  sleep,
  workouts,
  saveWorkout, // Expose the new function
};
Code collapsed

How it works

The saveWorkout function constructs an options object detailing the workout type, start and end times, and key metrics like calories burned and distance. It then calls AppleHealthKit.saveWorkout. If successful, the new workout will immediately appear in the user's Apple Health app.

Putting It All Together

Now we can use our powerful useHealthData hook in any component with very clean code.

Complete useHealthData.ts Hook

code
// src/hooks/useHealthData.ts
import AppleHealthKit, {
  HealthKitPermissions,
  HealthValue,
  SleepSample,
  WorkoutSample,
} from 'react-native-health';
import { useEffect, useState } from 'react';

const permissions: HealthKitPermissions = {
  permissions: {
    read: [
      AppleHealthKit.Constants.Permissions.Steps,
      AppleHealthKit.Constants.Permissions.SleepAnalysis,
      AppleHealthKit.Constants.Permissions.Workout,
    ],
    write: [
      AppleHealthKit.Constants.Permissions.Steps,
      AppleHealthKit.Constants.Permissions.SleepAnalysis,
      AppleHealthKit.Constants.Permissions.Workout,
    ],
  },
};

const useHealthData = () => {
  const [hasPermissions, setHasPermissions] = useState(false);
  const [steps, setSteps] = useState(0);
  const [sleep, setSleep] = useState<SleepSample[]>([]);
  const [workouts, setWorkouts] = useState<WorkoutSample[]>([]);

  useEffect(() => {
    AppleHealthKit.initHealthKit(permissions, (error: string) => {
      if (error) {
        console.log('[ERROR] Cannot grant permissions!');
        return;
      }
      setHasPermissions(true);
    });
  }, []);

  useEffect(() => {
    if (!hasPermissions) {
      return;
    }

    const options = {
      startDate: new Date(new Date().setDate(new Date().getDate() - 7)).toISOString(),
      endDate: new Date().toISOString(),
    };

    AppleHealthKit.getStepCount(options, (err, results: HealthValue) => {
      if (err) {
        console.log('Error getting step count:', err);
        return;
      }
      setSteps(results.value);
    });

    AppleHealthKit.getSleepSamples(options, (err, results: SleepSample[]) => {
      if (err) {
        console.log('Error getting sleep samples:', err);
        return;
      }
      setSleep(results);
    });

    AppleHealthKit.getAnchoredWorkouts(options, (err, results: WorkoutSample[]) => {
      if (err) {
        console.log('Error getting workout samples:', err);
        return;
      }
      setWorkouts(results);
    });
  }, [hasPermissions]);

  const saveWorkout = () => {
    if (!hasPermissions) {
      console.log('Cannot save workout without permissions.');
      return;
    }

    const options = {
      type: AppleHealthKit.Constants.Workouts.Running,
      startDate: new Date(new Date().getTime() - 60 * 60 * 1000).toISOString(),
      endDate: new Date().toISOString(),
      energyBurned: 350,
      totalDistance: 5,
    };

    AppleHealthKit.saveWorkout(options, (err, results) => {
      if (err) {
        console.log('Error saving workout:', err);
        return;
      }
      console.log('Workout saved successfully:', results);
    });
  };

  return {
    hasPermissions,
    steps,
    sleep,
    workouts,
    saveWorkout,
  };
};

export default useHealthData;
Code collapsed

Example App.tsx Component

code
// App.tsx
import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  useColorScheme,
  View,
  Button,
} from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import useHealthData from './src/hooks/useHealthData';

const App = () => {
  const isDarkMode = useColorScheme() === 'dark';
  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  const { steps, sleep, workouts, saveWorkout } = useHealthData();

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
        <View style={styles.container}>
          <Text style={styles.title}>HealthKit Demo</Text>

          <View style={styles.metricBox}>
            <Text style={styles.metricValue}>{steps.toLocaleString()}</Text>
            <Text style={styles.metricLabel}>Steps (Last 7 Days)</Text>
          </View>

          <View style={styles.metricBox}>
            <Text style={styles.metricValue}>{sleep.length}</Text>
            <Text style={styles.metricLabel}>Sleep Entries (Last 7 Days)</Text>
          </View>

          <View style={styles.metricBox}>
            <Text style={styles.metricValue}>{workouts.length}</Text>
            <Text style={styles.metricLabel}>Workouts (Last 7 Days)</Text>
          </View>
          
          <Button title="Save a Test Workout" onPress={saveWorkout} />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { padding: 20 },
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
  metricBox: {
    marginBottom: 15,
    padding: 15,
    borderRadius: 10,
    backgroundColor: '#f0f0f0',
  },
  metricValue: { fontSize: 20, fontWeight: '600' },
  metricLabel: { fontSize: 14, color: '#555', marginTop: 5 },
});

export default App;
Code collapsed

Security Best Practices

  • Request Only What You Need: Don't ask for permissions to data you don't use. This is suspicious to users and may cause them to deny access altogether.
  • Explain Why: Use the Info.plist descriptions to clearly and honestly explain why your app needs health data.
  • Handle Denied Permissions: If a user denies permission, your app should still function gracefully. Provide a button or guide that directs them to the Settings app where they can enable permissions manually. Remember, you can't re-prompt them from the app.

Conclusion

Integrating Apple HealthKit with a React Native app opens up a world of possibilities for creating engaging, personalized health and fitness applications. By using a library like react-native-health and encapsulating logic within a custom hook, you can manage the complexities of native configuration and permissions with clean, reusable code.

You've now learned how to:

  1. Configure Xcode with the HealthKit capability.
  2. Set up the necessary Info.plist permissions.
  3. Request user authorization using react-native-health.
  4. Read and write key health metrics like steps, sleep, and workouts.
  5. Structure your code in a scalable way with a custom hook.

Resources

#

Article Tags

reactnativeioshealthtechmobiledev
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