Статья

Локальный мост между HealthKit и Health Connect для Expo

Шаблон проектирования, объединяющий Apple HealthKit и Google Health Connect в одном переиспользуемом React-хуке без условий для платформ в интерфейсе.

Локальный мост между HealthKit и Health Connect для Expo

Всем привет! 👋 Если вы когда-нибудь пытались создать функцию для здоровья, фитнеса или другой утилиты в кроссплатформенном приложении на React Native, вы знаете эту боль: вы пишете не один набор функций, а боретесь с двумя совершенно разными тяжёлыми нативными экосистемами.

1. Начало

На iOS есть зрелый API Apple HealthKit. На Android — Health Connect, единый API-фреймворк Google, заменивший устаревший Google Fit SDK. Когда я проектировал архитектуру отслеживания воды для нового приложения WaterPaw, мне нужно было полностью модульное решение.

Я не хотел, чтобы условные проверки платформы (Platform.OS === 'ios') засоряли слой интерфейса. Мне нужен был один абстрактный пользовательский React-хук, который отвечает за доступность, разрешения и получение данных. Вот чистый шаблон проектирования, с помощью которого я их объединил; нативные настройки при этом автоматически применяются через Expo Config Plugins.

Сначала я определил конкретный контракт (интерфейс), чтобы фронтенду было всё равно, на какой нативной платформе он работает:

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

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

2. Конкретные мосты

Затем я вынес конкретные детали реализации в изолированные нативные обёртки.

  • Обёртка для iOS: Работает наreact-native-health для проверки статуса инициализации и передачи стандартных матриц разрешений HealthKit.
  • Обёртка для Android: Работает наreact-native-health-connect с обязательным двухшаговым процессом (проверка инициализации, затем отдельный контракт запроса разрешений).

3. Хук: динамический экспорт по платформе

Вся магия происходит при динамическом экспорте моста в зависимости от платформы: компоненты вызывают один-единственный пользовательский хук:

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. Главная ловушка Expo 💡

Оба нативных пакета используют сильно настроенный нативный код (Objective-C и Kotlin), поэтому они не скомпилируются в обычном Expo Go. Чтобы аккуратно обойти это без ручной работы в Xcode или Android Studio, нужно объявить config plugins в app.json. Обратите внимание, что Health Connect явно требует минимальную версию SDK 26:

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

После этого запуск npx expo prebuild --clean автоматически выполняет нативную генерацию.

Зачем столько усилий?

При создании минималистичных утилит важны производительность и отсутствие трения. Благодаря такой архитектуре интерфейс остаётся плавным на 60 FPS, обновления мгновенны, а поскольку подход local-first, приложение полностью уважает приватность данных пользователя. Никаких сторонних серверов и обязательной регистрации.

Эту структурную логику я использовал для умных напоминаний и системы развития талисмана в WaterPaw, которое я только что запустил!