こんにちは!👋 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は公開したばかりです!
