Artigo

Uma ponte local-first entre HealthKit e Health Connect no Expo

Um padrão para unificar Apple HealthKit e Google Health Connect em um único hook reutilizável do React, sem condicionais de plataforma na interface.

Uma ponte local-first entre HealthKit e Health Connect no Expo

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: Usareact-native-health para verificar a inicialização e solicitar as permissões padrão do HealthKit.
  • Wrapper do Android: Usareact-native-health-connect com 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!