JSPM

  • Created
  • Published
  • Downloads 59
  • Score
    100M100P100Q68864F
  • License MIT

Biblioteca completa para integração do Apple HealthKit com React Native/Expo (iOS) - Suporte a range de datas, consultas por número de dias e autorização seletiva

Package Exports

  • react-native-healthkit-bridge
  • react-native-healthkit-bridge/src/index.ts

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (react-native-healthkit-bridge) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

React Native HealthKit Bridge

Biblioteca completa para integração do Apple HealthKit com React Native/Expo (iOS). Permite acessar todos os tipos de dados de saúde disponíveis no HealthKit, incluindo dados do Apple Watch.

📋 Índice

🚀 Instalação

npm install react-native-healthkit-bridge

Para projetos Expo

npx expo install react-native-healthkit-bridge

⚙️ Configuração

1. Configuração do Xcode

  1. Abra seu projeto no Xcode
  2. Selecione seu target
  3. Vá em "Signing & Capabilities"
  4. Clique em "+ Capability"
  5. Adicione "HealthKit"

2. Configuração do Info.plist

Adicione as seguintes chaves no arquivo ios/YourApp/Info.plist:

<key>NSHealthShareUsageDescription</key>
<string>Este app precisa acessar seus dados de saúde para fornecer funcionalidades personalizadas e insights sobre sua saúde.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>Este app precisa atualizar seus dados de saúde para registrar suas atividades e manter um histórico completo.</string>

3. Instalação das Dependências

cd ios && pod install && cd ..

📱 Uso Básico

Importação

import HealthKitBridge from 'react-native-healthkit-bridge';

Solicitar Permissões

// Solicitar permissão para todos os tipos de dados
const isAuthorized = await HealthKitBridge.requestAuthorization();
console.log('Autorizado:', isAuthorized);

Obter Dados Básicos

// Passos de hoje
const todaySteps = await HealthKitBridge.getTodaySteps();
console.log('Passos de hoje:', todaySteps);

// Distância caminhada hoje
const todayDistance = await HealthKitBridge.getTodayDistance();
console.log('Distância hoje:', todayDistance);

// Calorias queimadas hoje
const todayCalories = await HealthKitBridge.getTodayCalories();
console.log('Calorias hoje:', todayCalories);

📊 Tipos de Dados

A biblioteca suporta todos os tipos de dados do HealthKit:

Dados Quantitativos

  • Passos, distância, calorias
  • Frequência cardíaca, pressão arterial
  • Peso, altura, IMC
  • Oxigenação do sangue
  • Temperatura corporal
  • E muito mais...

Dados Categóricos

  • Tipo de sangue
  • Data de nascimento
  • Sexo biológico
  • Condições médicas

Workouts

  • Corrida, caminhada, ciclismo
  • Natação, yoga, musculação
  • E todos os outros tipos de exercício

Dados Especiais

  • ECG (Apple Watch Series 4+)
  • Audiogramas
  • Registros clínicos

🎣 Hooks Personalizados

Hook para Passos Diários

import { useEffect, useState } from 'react';
import HealthKitBridge from 'react-native-healthkit-bridge';

export function useTodaySteps() {
  const [steps, setSteps] = useState<number | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchSteps() {
      try {
        setLoading(true);
        const isAuthorized = await HealthKitBridge.requestAuthorization();
        if (isAuthorized) {
          const todaySteps = await HealthKitBridge.getTodaySteps();
          setSteps(todaySteps);
        } else {
          setError('Permissão negada');
        }
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Erro desconhecido');
      } finally {
        setLoading(false);
      }
    }

    fetchSteps();
  }, []);

  return { steps, loading, error };
}

Hook para Dados de Saúde Completos

import { useEffect, useState } from 'react';
import HealthKitBridge from 'react-native-healthkit-bridge';

interface HealthData {
  steps: number;
  distance: number;
  calories: number;
  heartRate: number;
  weight: number;
  height: number;
}

export function useHealthData() {
  const [data, setData] = useState<HealthData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchHealthData() {
      try {
        setLoading(true);
        const isAuthorized = await HealthKitBridge.requestAuthorization();
      
        if (isAuthorized) {
          const [steps, distance, calories, heartRate, weight, height] = await Promise.all([
            HealthKitBridge.getTodaySteps(),
            HealthKitBridge.getTodayDistance(),
            HealthKitBridge.getTodayCalories(),
            HealthKitBridge.getLatestHeartRate(),
            HealthKitBridge.getLatestWeight(),
            HealthKitBridge.getLatestHeight()
          ]);

          setData({
            steps,
            distance,
            calories,
            heartRate,
            weight,
            height
          });
        } else {
          setError('Permissão negada');
        }
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Erro desconhecido');
      } finally {
        setLoading(false);
      }
    }

    fetchHealthData();
  }, []);

  return { data, loading, error };
}

💡 Exemplos Práticos

Dashboard de Saúde

import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { useHealthData } from './hooks/useHealthData';

export function HealthDashboard() {
  const { data, loading, error } = useHealthData();

  if (loading) {
    return (
      <View style={styles.container}>
        <Text>Carregando dados de saúde...</Text>
      </View>
    );
  }

  if (error) {
    return (
      <View style={styles.container}>
        <Text style={styles.error}>Erro: {error}</Text>
      </View>
    );
  }

  if (!data) {
    return (
      <View style={styles.container}>
        <Text>Nenhum dado disponível</Text>
      </View>
    );
  }

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>Dashboard de Saúde</Text>
    
      <View style={styles.card}>
        <Text style={styles.label}>Passos Hoje</Text>
        <Text style={styles.value}>{data.steps.toLocaleString()}</Text>
      </View>

      <View style={styles.card}>
        <Text style={styles.label}>Distância</Text>
        <Text style={styles.value}>{(data.distance / 1000).toFixed(2)} km</Text>
      </View>

      <View style={styles.card}>
        <Text style={styles.label}>Calorias</Text>
        <Text style={styles.value}>{data.calories.toLocaleString()}</Text>
      </View>

      <View style={styles.card}>
        <Text style={styles.label}>Frequência Cardíaca</Text>
        <Text style={styles.value}>{data.heartRate} bpm</Text>
      </View>

      <View style={styles.card}>
        <Text style={styles.label}>Peso</Text>
        <Text style={styles.value}>{data.weight} kg</Text>
      </View>

      <View style={styles.card}>
        <Text style={styles.label}>Altura</Text>
        <Text style={styles.value}>{data.height} cm</Text>
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  card: {
    backgroundColor: 'white',
    padding: 16,
    borderRadius: 8,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  label: {
    fontSize: 14,
    color: '#666',
    marginBottom: 4,
  },
  value: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
  },
  error: {
    color: 'red',
    textAlign: 'center',
  },
});

Monitor de Frequência Cardíaca

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import HealthKitBridge from 'react-native-healthkit-bridge';

export function HeartRateMonitor() {
  const [heartRate, setHeartRate] = useState<number | null>(null);
  const [status, setStatus] = useState<'normal' | 'elevated' | 'high'>('normal');

  useEffect(() => {
    async function monitorHeartRate() {
      try {
        const isAuthorized = await HealthKitBridge.requestAuthorization();
        if (isAuthorized) {
          const rate = await HealthKitBridge.getLatestHeartRate();
          setHeartRate(rate);
        
          if (rate < 60) setStatus('normal');
          else if (rate < 100) setStatus('elevated');
          else setStatus('high');
        }
      } catch (error) {
        console.error('Erro ao monitorar frequência cardíaca:', error);
      }
    }

    monitorHeartRate();
    const interval = setInterval(monitorHeartRate, 30000); // Atualiza a cada 30s

    return () => clearInterval(interval);
  }, []);

  const getStatusColor = () => {
    switch (status) {
      case 'normal': return '#4CAF50';
      case 'elevated': return '#FF9800';
      case 'high': return '#F44336';
      default: return '#666';
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Monitor Cardíaco</Text>
      <View style={[styles.heartRateCard, { borderColor: getStatusColor() }]}>
        <Text style={styles.label}>Frequência Cardíaca</Text>
        <Text style={[styles.value, { color: getStatusColor() }]}>
          {heartRate ? `${heartRate} bpm` : '--'}
        </Text>
        <Text style={[styles.status, { color: getStatusColor() }]}>
          {status === 'normal' && 'Normal'}
          {status === 'elevated' && 'Elevada'}
          {status === 'high' && 'Alta'}
        </Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 16,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  heartRateCard: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 12,
    borderWidth: 2,
    alignItems: 'center',
  },
  label: {
    fontSize: 16,
    color: '#666',
    marginBottom: 8,
  },
  value: {
    fontSize: 36,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  status: {
    fontSize: 14,
    fontWeight: '500',
  },
});

Histórico de Workouts

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import HealthKitBridge from 'react-native-healthkit-bridge';

interface Workout {
  id: string;
  type: string;
  startDate: string;
  endDate: string;
  duration: number;
  calories: number;
  distance?: number;
}

export function WorkoutHistory() {
  const [workouts, setWorkouts] = useState<Workout[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchWorkouts() {
      try {
        const isAuthorized = await HealthKitBridge.requestAuthorization();
        if (isAuthorized) {
          const workoutData = await HealthKitBridge.getWorkouts();
          setWorkouts(workoutData);
        }
      } catch (error) {
        console.error('Erro ao buscar workouts:', error);
      } finally {
        setLoading(false);
      }
    }

    fetchWorkouts();
  }, []);

  const renderWorkout = ({ item }: { item: Workout }) => (
    <View style={styles.workoutCard}>
      <Text style={styles.workoutType}>{item.type}</Text>
      <Text style={styles.workoutDate}>
        {new Date(item.startDate).toLocaleDateString('pt-BR')}
      </Text>
      <View style={styles.workoutStats}>
        <Text style={styles.stat}>
          Duração: {Math.round(item.duration / 60)}min
        </Text>
        <Text style={styles.stat}>
          Calorias: {item.calories}
        </Text>
        {item.distance && (
          <Text style={styles.stat}>
            Distância: {(item.distance / 1000).toFixed(2)}km
          </Text>
        )}
      </View>
    </View>
  );

  if (loading) {
    return (
      <View style={styles.container}>
        <Text>Carregando workouts...</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Histórico de Exercícios</Text>
      <FlatList
        data={workouts}
        renderItem={renderWorkout}
        keyExtractor={(item) => item.id}
        showsVerticalScrollIndicator={false}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  workoutCard: {
    backgroundColor: 'white',
    padding: 16,
    borderRadius: 8,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  workoutType: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
  },
  workoutDate: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  workoutStats: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginTop: 8,
  },
  stat: {
    fontSize: 12,
    color: '#888',
  },
});

🔧 API Completa

Métodos de Autorização

// Solicitar permissão para todos os tipos
await HealthKitBridge.requestAuthorization();

// Verificar se está autorizado
const isAuthorized = await HealthKitBridge.isAuthorized();

Métodos de Dados Quantitativos

// Dados de atividade
const steps = await HealthKitBridge.getTodaySteps();
const distance = await HealthKitBridge.getTodayDistance();
const calories = await HealthKitBridge.getTodayCalories();
const flights = await HealthKitBridge.getTodayFlights();

// Dados vitais
const heartRate = await HealthKitBridge.getLatestHeartRate();
const bloodPressure = await HealthKitBridge.getLatestBloodPressure();
const oxygenSaturation = await HealthKitBridge.getLatestOxygenSaturation();
const bodyTemperature = await HealthKitBridge.getLatestBodyTemperature();

// Dados corporais
const weight = await HealthKitBridge.getLatestWeight();
const height = await HealthKitBridge.getLatestHeight();
const bmi = await HealthKitBridge.getLatestBMI();
const bodyFat = await HealthKitBridge.getLatestBodyFat();

// Dados de sono
const sleepHours = await HealthKitBridge.getLastNightSleepHours();
const sleepAnalysis = await HealthKitBridge.getSleepAnalysis();

// Dados de exercício
const workouts = await HealthKitBridge.getWorkouts();
const activeEnergy = await HealthKitBridge.getTodayActiveEnergy();
const exerciseMinutes = await HealthKitBridge.getTodayExerciseMinutes();

Métodos com Range de Datas Personalizado

// Definir range de datas
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-01-31');

// Dados quantitativos com range
const stepsRange = await HealthKitBridge.getQuantitySamplesWithRange(
  'stepCount', 
  'count', 
  startDate.getTime() / 1000, 
  endDate.getTime() / 1000
);

const distanceRange = await HealthKitBridge.getQuantitySamplesWithRange(
  'distanceWalkingRunning', 
  'm', 
  startDate.getTime() / 1000, 
  endDate.getTime() / 1000
);

// Dados categóricos com range
const sleepRange = await HealthKitBridge.getCategorySamplesWithRange(
  'sleepAnalysis',
  startDate.getTime() / 1000,
  endDate.getTime() / 1000
);

// Workouts com range
const workoutsRange = await HealthKitBridge.getWorkoutsWithRange(
  startDate.getTime() / 1000,
  endDate.getTime() / 1000
);

// ECG com range
const ecgRange = await HealthKitBridge.getECGWithRange(
  startDate.getTime() / 1000,
  endDate.getTime() / 1000
);

// Audiogramas com range
const audiogramRange = await HealthKitBridge.getAudiogramsWithRange(
  startDate.getTime() / 1000,
  endDate.getTime() / 1000
);

Métodos por Número de Dias

// Dados quantitativos por número de dias
const steps90Days = await HealthKitBridge.getQuantitySamplesForDays(
  'stepCount', 
  'count', 
  90
);

const distance30Days = await HealthKitBridge.getQuantitySamplesForDays(
  'distanceWalkingRunning', 
  'm', 
  30
);

// Dados categóricos por número de dias
const sleep7Days = await HealthKitBridge.getCategorySamplesForDays(
  'sleepAnalysis',
  7
);

// Workouts por número de dias
const workouts365Days = await HealthKitBridge.getWorkoutsForDays(365);

// ECG por número de dias
const ecg30Days = await HealthKitBridge.getECGForDays(30);

// Audiogramas por número de dias
const audiogram7Days = await HealthKitBridge.getAudiogramsForDays(7);

Métodos de Dados Categóricos

// Dados pessoais
const bloodType = await HealthKitBridge.getBloodType();
const dateOfBirth = await HealthKitBridge.getDateOfBirth();
const biologicalSex = await HealthKitBridge.getBiologicalSex();

// Dados médicos
const conditions = await HealthKitBridge.getMedicalConditions();
const medications = await HealthKitBridge.getMedications();
const allergies = await HealthKitBridge.getAllergies();

Métodos de Dados Especiais

// ECG (Apple Watch Series 4+)
const ecgData = await HealthKitBridge.getLatestECG();

// Audiogramas
const audiogramData = await HealthKitBridge.getAudiogramData();

// Registros clínicos
const clinicalRecords = await HealthKitBridge.getClinicalRecords();

🛠️ Troubleshooting

Erro: "HealthKit não está disponível"

Causa: Tentando usar em simulador ou dispositivo sem HealthKit.

Solução: Use apenas em dispositivos físicos iOS.

Erro: "Permissão negada"

Causa: Usuário não concedeu permissão.

Solução:

  1. Verifique se as descrições no Info.plist estão claras
  2. Oriente o usuário a ir em Configurações > Privacidade > Saúde
  3. Reimplemente a solicitação de permissão

Erro: "Dados não encontrados"

Causa: Não há dados do tipo solicitado no HealthKit.

Solução: Sempre verifique se os dados existem antes de usá-los:

try {
  const heartRate = await HealthKitBridge.getLatestHeartRate();
  if (heartRate !== null) {
    console.log('Frequência cardíaca:', heartRate);
  } else {
    console.log('Nenhum dado de frequência cardíaca disponível');
  }
} catch (error) {
  console.error('Erro ao buscar frequência cardíaca:', error);
}

Erro: "Build falhou"

Causa: Problemas de configuração do Xcode.

Solução:

  1. Limpe o build: cd ios && xcodebuild clean && cd ..
  2. Reinstale os pods: cd ios && pod install && cd ..
  3. Rebuild o projeto

⚠️ Limitações

  • Apenas iOS: HealthKit é exclusivo da Apple
  • Dispositivo Físico: Não funciona em simuladores
  • Permissões: Usuário deve conceder permissão explicitamente
  • Dados Limitados: Alguns dados dependem do hardware (ex: ECG)
  • Versão iOS: Requer iOS 8.0 ou superior

🤝 Contribuição

  1. Fork o projeto
  2. Crie uma branch para sua feature (git checkout -b feature/AmazingFeature)
  3. Commit suas mudanças (git commit -m 'Add some AmazingFeature')
  4. Push para a branch (git push origin feature/AmazingFeature)
  5. Abra um Pull Request

📄 Licença

Este projeto está licenciado sob a Licença MIT - veja o arquivo LICENSE para detalhes.

📞 Suporte

🙏 Agradecimentos

  • Apple HealthKit Team
  • React Native Community
  • Todos os contribuidores

⭐ Se este projeto te ajudou, considere dar uma estrela no GitHub!