Olá, pessoal! 👋 Se você já tentou criar um recurso de saúde, fitness ou utilidade em um app multiplataforma com React Native, conhece o desafio: não está desenvolvendo um único conjunto de recursos, mas lidando com dois ecossistemas nativos pesados e totalmente separados.
1. Introdução
No iOS, temos a API madura Apple HealthKit. No Android, há o Health Connect, o framework unificado de APIs do Google que substituiu o antigo SDK do Google Fit. Ao criar a arquitetura de acompanhamento de hidratação para meu novo app, o WaterPaw, eu queria uma solução totalmente modular.
Eu não queria que verificações de plataforma como Platform.OS === 'ios'complicassem a interface. Queria um único hook abstrato do React para gerenciar disponibilidade, permissões e busca de dados. Este é o padrão que usei para unificar as plataformas, com Expo Config Plugins cuidando automaticamente das configurações nativas.
Primeiro, defini um contrato concreto (uma interface) para que o frontend não precisasse saber em qual plataforma nativa está sendo executado:
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
Depois, isolei os detalhes de cada implementação em wrappers nativos separados.
- Wrapper do iOS: Usa
react-native-healthpara verificar a inicialização e solicitar as permissões padrão do HealthKit. - Wrapper do Android: Usa
react-native-health-connectcom o fluxo obrigatório em duas etapas: verificar a inicialização e, em seguida, iniciar separadamente a solicitação de permissões.
3. O hook: exportação dinâmica por plataforma
A solução é exportar a bridge dinamicamente conforme a plataforma, permitindo que os componentes chamem um ú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. A particularidade importante do Expo 💡
Como os dois pacotes nativos usam código nativo bastante personalizado (Objective-C e Kotlin), eles não compilam no Expo Go padrão. Para contornar isso sem mexer manualmente no Xcode ou no Android Studio, é preciso declarar config plugins no app.json. O Health Connect exige explicitamente o SDK mínimo 26:
{
"expo": {
"plugins": [
["react-native-health", { "NSHealthShareUsageDescription": "Sync your hydration records." }],
["expo-health-connect"],
[
"expo-build-properties",
{ "android": { "minSdkVersion": 26 } }
]
]
}
}Depois dessa alteração, o comando npx expo prebuild --clean gera o código nativo automaticamente.
Por que todo esse trabalho?
Ao criar utilitários minimalistas, desempenho e ausência de atrito são essenciais. Com essa arquitetura, a interface continua fluida a 60 FPS, as atualizações são instantâneas e, por ser local-first, o app respeita totalmente a privacidade dos dados do usuário. Sem servidores de terceiros ou cadastro obrigatório.
Usei essa estrutura para alimentar os lembretes inteligentes e a evolução do mascote no WaterPaw, que acabei de lançar!
