react-native-healthkit

repository·master·Indexed 20 days ago

https://github.com/kingstinct/react-native-healthkit

A high-performance React Native binding for Apple's HealthKit featuring full TypeScript support and Promise-based APIs. It provides hooks and imperative APIs for querying, saving, and subscribing to health data, including quantity, category, workout, and medication samples. The library supports HealthKit anchors for efficient data syncing and includes an Expo Config Plugin for automating entitlements and Info.plist usage descriptions.

Tokens
12.8K
Snippets
41
Records
59
Agent score
72%

What's inside @kingstinct/react-native-healthkit

  1. Best practices for interface verification

    master

    When using the Interface Verification Utility in your project, follow these patterns to ensure effective type safety:

    1. Always exclude inherited keys: When defining the ExcludeFromBase parameter, use keyof HybridObject or similar to ensure you are only comparing your custom methods, not the bridge's internal methods.
    2. Add verification immediately: Place the InterfaceAssertion line directly after your interface definitions to catch errors as soon as they are introduced.
    3. Use descriptive naming: Use a prefix like _verification for the constant holding the assertion to signal to other developers that this line is for type-checking purposes only.
    4. Keep interfaces close: Define both the base and the typed interface in the same file whenever possible to maintain visibility and ease of maintenance.
  2. Syncing data with HealthKit Anchors

    master

    Since version 6.0, you can use HealthKit anchors to efficiently sync data and identify deleted items. An anchor is a base64-encoded string returned by HealthKit that contains sync information.

    When performing a query, store the newAnchor returned in the result. For the next sync, pass this anchor into your query to only receive changes since the last sync.

    • newAnchor: The anchor to use for the next query.
    • samples: The array of retrieved samples.
    • deletedSamples: The array of samples that were deleted.
    • limit: An option to restrict the number of records considered (set to 0 for no limit).
    // Initial query
    const { newAnchor, samples, deletedSamples } = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
      limit: 2,
    });
    
    // Subsequent query using the anchor
    const nextResult = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
      limit: 2,
      anchor: newAnchor,
    });
    const { newAnchor, samples, deletedSamples } = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
      limit: 2,
    });
    
    const nextResult = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
      limit: 2,
      anchor: newAnchor,
    });
  3. Request HealthKit authorization and avoid crashes

    master

    Before querying or subscribing to HealthKit data, you must request authorization. Failing to request authorization, or attempting to access a permission you haven't requested, will cause the app to crash.

    Important: Do not request authorization in the same component where a hook is attempting to fetch data immediately upon mount. Ensure the authorization process completes before data hooks are active.

    import { useHealthkitAuthorization } from '@kingstinct/react-native-healthkit';
    
    // Use the hook to manage authorization status and the request function
    const [authorizationStatus, requestAuthorization] = useHealthkitAuthorization(['HKQuantityTypeIdentifierBloodGlucose']);
  4. Install @kingstinct/react-native-healthkit in Native or Expo Bare Workflow

    master

    For native projects or Expo Bare workflow, follow these steps:

    1. Install dependencies: yarn add @kingstinct/react-native-healthkit react-native-nitro-modules

    2. Install native pods: npx pod-install

    3. Configure Info.plist: Add NSHealthUpdateUsageDescription and NSHealthShareUsageDescription keys with appropriate usage descriptions.

    4. Xcode Configuration:

      • Enable the HealthKit capability for your project.
      • If your project does not already have a bridging header, you may need to add one to support the Swift implementation.
    yarn add @kingstinct/react-native-healthkit react-native-nitro-modules
    npx pod-install
  5. Use the Interface Verification Utility to sync interfaces

    master

    The Interface Verification Utility is a TypeScript-only tool designed to ensure that a base interface (typically used for a native bridge, like one extending HybridObject) and a typed interface (used for better type safety with generics) remain in sync. It catches mismatches in method names and parameter counts at compile-time with zero runtime cost.

    How it works

    • Method name verification: Ensures both interfaces contain the same methods.
    • Parameter count verification: Ensures corresponding methods have the same number of arguments.
    • Compile-time checking: Errors are surfaced by TypeScript during development.

    Quick Start Example

    To verify your interfaces, define your base and typed versions, then use InterfaceAssertion to perform the check:

    import type { InterfaceAssertion } from "../types/InterfaceVerification";
    
    // 1. Base interface (e.g., for the native bridge)
    export interface MyModule extends HybridObject<{ ios: "swift" }> {
    	getData(id: string): Promise<string>;
    	saveData(id: string, data: string): Promise<boolean>;
    }
    
    // 2. Typed interface (with generics for better DX)
    export interface MyModuleTyped {
    	getData<T extends string>(id: T): Promise<string>;
    	saveData<T extends string>(id: T, data: string): Promise<boolean>;
    }
    
    // 3. Verification - will cause a TypeScript error if interfaces don't match
    // Note: Always exclude inherited keys like `keyof HybridObject`
    const _verification: InterfaceAssertion<MyModule, MyModuleTyped, keyof HybridObject> = true;
    import type { InterfaceAssertion } from "../types/InterfaceVerification";
    
    export interface MyModule extends HybridObject<{ ios: "swift" }> {
    	getData(id: string): Promise<string>;
    	saveData(id: string, data: string): Promise<boolean>;
    }
    
    export interface MyModuleTyped {
    	getData<T extends string>(id: T): Promise<string>;
    	saveData<T extends string>(id: T, data: string): Promise<boolean>;
    }
    
    const _verification: InterfaceAssertion<MyModule, MyModuleTyped, keyof HybridObject> = true;
  6. Install @kingstinct/react-native-healthkit in Expo

    master

    To use this library in an Expo project, you must use a Development Client (it will not work in Expo Go).

    1. Install the required packages: yarn add @kingstinct/react-native-healthkit react-native-nitro-modules

    2. Configure the Expo config plugin in your app.json. You can use the defaults or provide custom usage descriptions for HealthKit permissions and enable background access:

    {
      "expo": {
        "plugins": [
          [
            "@kingstinct/react-native-healthkit",
            {
              "NSHealthShareUsageDescription": "Your own custom usage description",
              "NSHealthUpdateUsageDescription": "Your own custom usage description",
              "background": true
            }
          ]
        ]
      }
    }
    1. Rebuild your Dev Client.
  7. Imperative API: Querying, Subscribing, and Saving data

    master

    For non-hook usage, use the imperative API. Always check availability and request permissions first.

    Check Availability

    await isHealthDataAvailable()

    Read Data

    await getMostRecentQuantitySample(identifier) returns an object containing { quantity, unit, startDate, endDate }.

    Subscribe to Changes

    subscribeToChanges(identifier, callback) allows you to react to data updates. Remember to unsubscribe in the cleanup function of your effect.

    Write Data

    saveQuantitySample(identifier, unit, value, options) allows saving new samples. Metadata keys can be arbitrary strings or built-in HealthKit keys (use the string representation of the key, e.g., HKInsulinDeliveryReason instead of a variable).

    import {
      isHealthDataAvailable,
      requestAuthorization,
      subscribeToChanges,
      saveQuantitySample,
      getMostRecentQuantitySample
    } from '@kingstinct/react-native-healthkit';
    
    // 1. Check availability
    const isAvailable = await isHealthDataAvailable();
    
    // 2. Request Read Permission
    await requestAuthorization({ toRead: ['HKQuantityTypeIdentifierBodyFatPercentage'] });
    
    // 3. Read latest sample
    const { quantity, unit, startDate, endDate } = await getMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage');
    
    // 4. Subscribe to changes
    const unsubscribe = subscribeToChanges('HKQuantityTypeIdentifierHeartRate', () => {
      // refetch data
    });
    
    // 5. Write data
    await requestAuthorization({ toShare: ['HKQuantityTypeIdentifierInsulinDelivery'] });
    await saveQuantitySample(
      'HKQuantityTypeIdentifierInsulinDelivery',
      'IU',
      5.5,
      {
        metadata: {
          HKInsulinDeliveryReason: HKInsulinDeliveryReason.basal,
        },
      }
    );
    import {
      isHealthDataAvailable,
      requestAuthorization,
      subscribeToChanges,
      saveQuantitySample,
      getMostRecentQuantitySample
    } from '@kingstinct/react-native-healthkit';
    
    const isAvailable = await isHealthDataAvailable();
    
    await requestAuthorization({ toRead: ['HKQuantityTypeIdentifierBodyFatPercentage'] });
    const { quantity, unit, startDate, endDate } = await getMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage');
    
    const unsubscribe = subscribeToChanges('HKQuantityTypeIdentifierHeartRate', () => {
      // refetch data
    });
    
    await requestAuthorization({ toShare: ['HKQuantityTypeIdentifierInsulinDelivery'] });
    await saveQuantitySample(
      'HKQuantityTypeIdentifierInsulinDelivery',
      'IU',
      5.5,
      {
        metadata: {
          HKInsulinDeliveryReason: HKInsulinDeliveryReason.basal,
        },
      }
    );
  8. Available Quantity Types by iOS Version

    master

    The available QuantityTypeIdentifier values depend on the user's iOS version. Specifically, certain quantity types are only available on iOS 17 or later.

    iOS 17+ Exclusive Types:

    • HKQuantityTypeIdentifierCyclingCadence
    • HKQuantityTypeIdentifierCyclingFunctionalThresholdPower
    • HKQuantityTypeIdentifierCyclingPower
    • HKQuantityTypeIdentifierCyclingSpeed
    • HKQuantityTypeIdentifierPhysicalEffort
    • HKQuantityTypeIdentifierTimeInDaylight

    Use the AvailableQuantityTypes<T> type to ensure type safety when writing code that targets specific iOS versions.

  9. Extend query options with sorting, anchors, and units

    master

    Depending on the specific query method being used, you can use specialized versions of GenericQueryOptions to add functionality:

    • Sorting: Use QueryOptionsWithSortOrder to add an ascending?: boolean flag.
    • Pagination/Anchors: Use QueryOptionsWithAnchor to add an anchor?: string for cursor-based fetching.
    • Units: Use QueryOptionsWithSortOrderAndUnit or QueryOptionsWithAnchorAndUnit to specify a unit?: TUnit (e.g., for converting measurements during the query).
    export interface QueryOptionsWithAnchor extends GenericQueryOptions {
      readonly anchor?: string
    }
    
    export interface QueryOptionsWithSortOrder extends GenericQueryOptions {
      readonly ascending?: boolean
    }
    
    export interface QueryOptionsWithSortOrderAndUnit<TUnit extends string = string>
      extends QueryOptionsWithSortOrder {
      readonly unit?: TUnit
    }
    
    export interface QueryOptionsWithAnchorAndUnit<TUnit extends string = string>
      extends QueryOptionsWithAnchor {
      readonly unit?: TUnit
    }
  10. Configure @kingstinct/react-native-healthkit with Expo Config Plugins

    master

    To use @kingstinct/react-native-healthkit in an Expo project (specifically in the Bare Workflow or via Continuous Native Generation), you must use the provided Expo Config Plugin. This plugin automates the configuration of Apple HealthKit entitlements and Info.plist usage descriptions.

    You can apply the plugin in your app.json or app.config.js using the plugins array. The plugin accepts an options object to configure background delivery and usage description strings.

    {
      "expo": {
        "plugins": [
          [
            "@kingstinct/react-native-healthkit",
            {
              "NSHealthShareUsageDescription": "This app needs access to your health data to track your workouts.",
              "NSHealthUpdateUsageDescription": "This app needs to write health data to help you stay on track.",
              "background": true
            }
          ]
        ]
      }
    }