¡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: Usa
react-native-healthpara comprobar la inicialización y solicitar los permisos habituales de HealthKit. - Wrapper de Android: Usa
react-native-health-connectcon 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!
