Articolo

Un ponte locale tra HealthKit e Health Connect per Expo

Un design pattern per unificare Apple HealthKit e Google Health Connect dietro un unico hook React riutilizzabile, senza condizionali di piattaforma nell’interfaccia.

Un ponte locale tra HealthKit e Health Connect per Expo

Ciao a tutti! 👋 Se hai mai provato a sviluppare una funzione di salute, fitness o utilità in un’app React Native multipiattaforma, conosci il problema: non stai scrivendo un unico insieme di funzionalità, stai lottando con due ecosistemi nativi completamente separati e pesanti.

1. Punto di partenza

Su iOS c’è la matura API Apple HealthKit. Su Android c’è Health Connect, il framework API unificato di Google che ha sostituito il vecchio SDK di Google Fit. Quando sviluppavo l’architettura di monitoraggio dell’idratazione per la mia nuova app, WaterPaw, volevo una soluzione completamente modulare.

Non volevo controlli condizionali di piattaforma (Platform.OS === 'ios') a disturbare il livello dell’interfaccia. Volevo un unico hook React personalizzato e astratto che gestisse disponibilità, permessi e recupero dei dati in un solo punto. Ecco il design pattern pulito che ho usato per unificarli, sfruttando gli Expo Config Plugin per gestire automaticamente le configurazioni native.

Per prima cosa ho definito un modello concreto (interfaccia) in modo che il frontend non si preoccupi della piattaforma nativa su cui gira:

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

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

2. Bridge concreti

Poi ho separato i dettagli esatti dell’implementazione in wrapper nativi isolati.

  • Wrapper iOS: Basato sureact-native-health per verificare lo stato di inizializzazione e passare le matrici di permessi standard di HealthKit.
  • Wrapper Android: Basato sureact-native-health-connect con il flusso di esecuzione in due passaggi richiesto (verifica dell’inizializzazione seguita da un contratto separato per l’intent dei permessi).

3. L’hook: esportazione dinamica per piattaforma

La magia avviene esportando il bridge in modo dinamico in base alla piattaforma, così i componenti possono richiamare un unico hook personalizzato:

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. Il grande tranello di Expo 💡

Poiché entrambi i pacchetti nativi usano codice nativo molto personalizzato (Objective-C e Kotlin), non si compilano all’interno del normale Expo Go. Per aggirare il problema in modo pulito, senza armeggiare manualmente in Xcode o Android Studio, devi dichiarare i config plugin nel tuo app.json. Nota che Health Connect richiede esplicitamente un SDK minimo 26:

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

Dopo aver aggiornato il file, eseguire npx expo prebuild --clean gestisce automaticamente la generazione nativa.

Perché prendersi tutto questo disturbo?

Quando si sviluppano utility minimaliste, prestazioni e zero attriti sono tutto. Con questa architettura l’interfaccia resta fluida a 60 FPS, gli aggiornamenti sono istantanei e, essendo local-first, l’app rispetta pienamente la privacy dei dati dell’utente. Nessun server di terze parti, nessuna registrazione obbligatoria.

Ho usato questa logica strutturale per alimentare i promemoria intelligenti e i sistemi di progressione della mascotte all’interno di WaterPaw, che ho appena lanciato!