Artículo

Un puente local-first entre HealthKit y Health Connect con Expo

Un patrón para unificar Apple HealthKit y Google Health Connect detrás de un único hook reutilizable de React, sin condicionales de plataforma en la interfaz.

Un puente local-first entre HealthKit y Health Connect con Expo

¡Hola a todos! 👋 Si alguna vez habéis intentado crear una función de salud, fitness o utilidad en una app multiplataforma de React Native, conocéis el problema: no estáis desarrollando un único conjunto de funciones, sino lidiando con dos ecosistemas nativos independientes y complejos.

1. Introducción

En iOS contamos con la madura API de Apple HealthKit. En Android está Health Connect, el framework unificado de APIs de Google que sustituyó al antiguo SDK de Google Fit. Al crear la arquitectura de seguimiento de hidratación para mi nueva app WaterPaw, quería una solución totalmente modular.

No quería que comprobaciones de plataforma como Platform.OS === 'ios'complicaran la interfaz. Quería un único hook abstracto de React que gestionara disponibilidad, permisos y obtención de datos. Este es el patrón que utilicé para unificarlos, con Expo Config Plugins para automatizar la configuración nativa.

Primero definí un contrato concreto (una interfaz) para que el frontend no tuviera que saber en qué plataforma nativa se ejecuta:

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

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

2. Bridges concretos

Después aislé los detalles exactos de cada implementación en wrappers nativos separados.

  • Wrapper de iOS: Usareact-native-health para comprobar la inicialización y solicitar los permisos habituales de HealthKit.
  • Wrapper de Android: Usareact-native-health-connect con el flujo obligatorio de dos pasos: comprobar la inicialización y después lanzar por separado la solicitud de permisos.

3. El hook: exportación dinámica según la plataforma

La clave está en exportar dinámicamente el bridge según la plataforma, para que los componentes llamen a un único hook personalizado:

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. La particularidad importante de Expo 💡

Como ambos paquetes nativos utilizan código muy personalizado (Objective-C y Kotlin), no se pueden compilar en Expo Go estándar. Para evitarlo sin tener que modificar manualmente Xcode o Android Studio, hay que declarar config plugins en app.json. Health Connect requiere explícitamente como mínimo el SDK 26:

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

Después de actualizarlo, el comando npx expo prebuild --clean genera el código nativo automáticamente.

¿Por qué complicarse tanto?

Al crear utilidades minimalistas, el rendimiento y la ausencia de fricción lo son todo. Con esta arquitectura, la interfaz se mantiene fluida a 60 FPS, las actualizaciones son instantáneas y, al ser local-first, la app respeta por completo la privacidad de los datos. Sin servidores de terceros ni registro obligatorio.

Utilicé esta estructura para implementar los recordatorios inteligentes y la progresión de la mascota de WaterPaw, ¡que acabo de lanzar!