記事

ExpoでHealthKitとHealth Connectをつなぐローカル優先ブリッジ

Apple HealthKitとGoogle Health Connectを1つのReactフックで統合し、UIのプラットフォーム条件分岐をなくす設計パターンです。

ExpoでHealthKitとHealth Connectをつなぐローカル優先ブリッジ

こんにちは!👋 React Nativeのクロスプラットフォームアプリで健康、フィットネス、ユーティリティ機能を作ろうとしたことがあれば、その難しさをご存じでしょう。1つの機能を開発するのではなく、まったく異なる2つの大規模なネイティブ環境に対応することになります。

1. はじめに

iOSには成熟したApple HealthKit APIがあります。Androidには、従来のGoogle Fit SDKに代わるGoogleの統合APIフレームワーク、Health Connectがあります。新しいアプリWaterPawの水分補給トラッキングを設計する際、完全にモジュール化された方法を目指しました。

UI層に 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 です。初期化を確認した後、権限要求を別途実行する2段階のフローが必要です。

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を手作業で変更せずに対応するには、app.jsonでConfig Pluginsを宣言します。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を実行するとネイティブコードが自動生成されます。

なぜここまで必要なのか?

ミニマルなユーティリティでは、性能と使いやすさがすべてです。この設計ならUIは60 FPSで滑らかに動き、更新は即時に反映されます。ローカル優先のため、ユーザーのデータプライバシーも完全に尊重できます。サードパーティサーバーも、必須アカウント登録もありません。

この構成をWaterPawのスマートリマインダーとマスコットの成長機能に採用しました。WaterPawは公開したばかりです!