# How to Integrate Wearable Tech Data into a FlutterFlow Health App

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 40-60 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## TL;DR

Read wearable health data in FlutterFlow using the health Flutter package in a Custom Action. Call Health.requestAuthorization with the data types you need (STEPS, HEART_RATE, SLEEP_ASLEEP, WORKOUT), then getHealthDataFromTypes with a date range. The package reads from Apple Health on iOS and Google Fit on Android — one integration covers both platforms. Always show an explanation screen before requesting permissions on iOS or the permission dialog appears blank and users deny it.

## Apple Health and Google Fit integration via the health Flutter package

FlutterFlow does not have a built-in health data integration, but the health Flutter package provides a unified API for reading health and fitness data from Apple Health (HealthKit) on iOS and Google Health Connect on Android. This means one Custom Action implementation reads step counts, heart rate, sleep duration, calories burned, and workout sessions from any connected wearable — Apple Watch, Fitbit, Garmin, WHOOP, or any other device that syncs to Apple Health or Google Fit. This tutorial builds a complete health dashboard: permission request flow, data fetching, Firestore sync for persistence, and a KPI card + chart display in FlutterFlow.

## Before you start

- A FlutterFlow project with Firebase connected
- Physical iOS or Android device with at least one health app installed (Apple Health on iPhone, Google Health Connect on Android)
- FlutterFlow Free plan or above — health Custom Actions work on all plans
- Basic understanding of Custom Actions and Pubspec Dependencies in FlutterFlow

## Step-by-step guide

### 1. Add the health package and configure platform permissions

In FlutterFlow, go to Custom Code → Pubspec Dependencies. Add health (version ^10.2.0). Click Save and wait for compile. For iOS, go to Settings → iOS Settings → Info.plist Custom Values. Add NSHealthShareUsageDescription with value 'This app reads your health data to track your daily wellness metrics'. Add NSHealthUpdateUsageDescription with value 'This app may save workout data to your Health app'. For Android, go to Settings → Android Settings → Manifest. Add the ACTIVITY_RECOGNITION permission and the HEALTH_READ permission. Also add the health_connect activity declaration to AndroidManifest.xml (check the health package pub.dev page for the exact XML snippet for your version).

**Expected result:** The health package compiles successfully and both iOS Info.plist and Android Manifest have the required health permission entries.

### 2. Create the permission request flow with explanation screen

In FlutterFlow, create a HealthPermissionsExplainer page. Add a Column with: a health/heart icon, a heading 'Connect Your Health Data', a body text explaining exactly what data will be accessed ('We will read your daily steps, heart rate, and sleep data to show you personalized wellness insights'), and a green Connect button. Add a 'No thanks' text button. On the Connect button tap: call a Custom Action requestHealthPermissions. In the Custom Action, create a Health instance and call Health.requestAuthorization with the list of HealthDataType values you need. After authorization, navigate to the main health dashboard page. On 'No thanks', navigate to the dashboard without health data (show placeholder empty state). Navigate to this explainer page on first app launch when health permissions have not been requested yet (check a hasSeenHealthExplainer boolean in SharedPreferences).

**Expected result:** First-time users see the explanation screen before any iOS permission dialog appears. Users who tap 'No thanks' see the dashboard without health data.

### 3. Create the fetchHealthData Custom Action

Create a Custom Action named fetchHealthData that takes a String parameter dateRange ('today', '7days', '30days'). Compute startDate as midnight of the start date (DateTime.now().subtract(Duration(days: 7)).startOf('day')) and endDate as now. Call Health.getHealthDataFromTypes(startDate, endDate, [HealthDataType.STEPS, HealthDataType.HEART_RATE, HealthDataType.SLEEP_ASLEEP, HealthDataType.WORKOUT]). The method returns a list of HealthDataPoint objects. Aggregate steps by summing all STEPS data points for the day, calculate average heart rate from HEART_RATE points, calculate total sleep hours from SLEEP_ASLEEP points (sum durations and convert to hours), and count workouts. Return the aggregated values as a JSON object. Store the JSON in App State healthData for the dashboard to display.

```
import 'package:health/health.dart';

Future<Map<String, dynamic>> fetchHealthData(String dateRange) async {
  final health = Health();
  final now = DateTime.now();
  final days = dateRange == '30days' ? 30 : dateRange == '7days' ? 7 : 1;
  final startDate = DateTime(now.year, now.month, now.day)
      .subtract(Duration(days: days - 1));

  final types = [
    HealthDataType.STEPS,
    HealthDataType.HEART_RATE,
    HealthDataType.SLEEP_ASLEEP,
    HealthDataType.WORKOUT,
  ];

  bool authorized = await health.requestAuthorization(types);
  if (!authorized) return {'error': 'Not authorized'};

  final dataPoints = await health.getHealthDataFromTypes(
    startTime: startDate,
    endTime: now,
    types: types,
  );

  int totalSteps = 0;
  List<double> heartRates = [];
  double sleepHours = 0;
  int workouts = 0;

  for (final point in dataPoints) {
    final value = (point.value as NumericHealthValue).numericValue;
    switch (point.type) {
      case HealthDataType.STEPS:
        totalSteps += value.toInt();
        break;
      case HealthDataType.HEART_RATE:
        heartRates.add(value.toDouble());
        break;
      case HealthDataType.SLEEP_ASLEEP:
        sleepHours += point.dateTo.difference(point.dateFrom).inMinutes / 60;
        break;
      case HealthDataType.WORKOUT:
        workouts++;
        break;
      default:
        break;
    }
  }

  return {
    'steps': totalSteps,
    'avgHeartRate': heartRates.isEmpty ? 0 :
        (heartRates.reduce((a, b) => a + b) / heartRates.length).round(),
    'sleepHours': double.parse(sleepHours.toStringAsFixed(1)),
    'workouts': workouts,
    'fetchedAt': now.toIso8601String(),
  };
}
```

**Expected result:** Custom Action returns a JSON object with steps, average heart rate, sleep hours, and workout count for the specified date range.

### 4. Build the health dashboard with KPI cards and trend display

Create a HealthDashboard page. In the page's On Page Load action, call the fetchHealthData Custom Action with '7days' and store the result in App State variable healthData (JSON type). Add a Row of four Container KPI cards: Steps (show healthData.steps), Heart Rate (show healthData.avgHeartRate with 'bpm'), Sleep (show healthData.sleepHours with 'hrs'), Workouts (show healthData.workouts). Each card uses a Column with a large bold number on top and a label below. For color coding: if steps > 10000 show green icon, else orange. If sleepHours > 7 show green, else orange. Add ChoiceChips below for Today / 7 Days / 30 Days date range selection — on chip change, call fetchHealthData with the new range. Add a CircularProgressIndicator while the action is loading by checking an isLoading Page State boolean.

**Expected result:** Health dashboard page shows four KPI cards with real data from the user's wearable. Switching date ranges updates all metrics.

### 5. Sync health data to Firestore for persistence and analytics

Modify the fetchHealthData Custom Action to also write the aggregated health metrics to Firestore after fetching. Create a Firestore collection health_data with documents keyed by userId + date (e.g., userId_2026-03-29). Write: userId, date, steps, avgHeartRate, sleepHours, workouts, source ('apple_health' or 'google_fit' based on Platform.isIOS), syncedAt. This creates a historical record of daily health metrics that persists across devices and enables analytics (weekly trends, streaks, goals). For trend charts: query health_data where userId == current user ordered by date, last 30 records. Plot steps as a bar chart using a Custom Widget with fl_chart. This data syncing should happen in the background on app open, not blocking the UI.

**Expected result:** Each app open syncs today's health data to Firestore. Historical health_data documents accumulate for trend analysis and cross-device access.

## Complete code example

File: `fetch_health_data.dart`

```dart
// Custom Action: fetchHealthData
// Reads from Apple Health (iOS) or Google Health Connect (Android)
// Returns aggregated daily health metrics

import 'package:health/health.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<Map<String, dynamic>> fetchHealthData(String dateRange) async {
  final health = Health();
  final now = DateTime.now();
  final days = dateRange == '30days' ? 30 : dateRange == '7days' ? 7 : 1;
  final startDate = DateTime(now.year, now.month, now.day)
      .subtract(Duration(days: days - 1));

  final types = [
    HealthDataType.STEPS,
    HealthDataType.HEART_RATE,
    HealthDataType.SLEEP_ASLEEP,
    HealthDataType.ACTIVE_ENERGY_BURNED,
  ];

  bool authorized = await health.requestAuthorization(types);
  if (!authorized) return {'error': 'not_authorized', 'steps': 0};

  final dataPoints = await health.getHealthDataFromTypes(
    startTime: startDate,
    endTime: now,
    types: types,
  );

  int totalSteps = 0;
  List<double> heartRates = [];
  double sleepHours = 0;
  double caloriesBurned = 0;

  for (final point in Health.removeDuplicates(dataPoints)) {
    final value = (point.value as NumericHealthValue).numericValue;
    switch (point.type) {
      case HealthDataType.STEPS:
        totalSteps += value.toInt();
        break;
      case HealthDataType.HEART_RATE:
        heartRates.add(value.toDouble());
        break;
      case HealthDataType.SLEEP_ASLEEP:
        sleepHours += point.dateTo.difference(point.dateFrom).inMinutes / 60;
        break;
      case HealthDataType.ACTIVE_ENERGY_BURNED:
        caloriesBurned += value.toDouble();
        break;
      default:
        break;
    }
  }

  final result = {
    'steps': totalSteps,
    'avgHeartRate': heartRates.isEmpty ? 0 :
        (heartRates.reduce((a, b) => a + b) / heartRates.length).round(),
    'sleepHours': double.parse(sleepHours.toStringAsFixed(1)),
    'calories': caloriesBurned.round(),
    'fetchedAt': now.toIso8601String(),
  };

  // Persist to Firestore for history and cross-device access
  final uid = FirebaseAuth.instance.currentUser?.uid;
  if (uid != null && dateRange == 'today') {
    final today = '${now.year}-${now.month.toString().padLeft(2,'0')}-${now.day.toString().padLeft(2,'0')}';
    await FirebaseFirestore.instance
        .collection('health_data')
        .doc('${uid}_$today')
        .set({...result, 'userId': uid, 'date': today}, SetOptions(merge: true));
  }

  return result;
}
```

## Common mistakes

- **Requesting health data permissions immediately on app launch without showing an explanation screen first** — iOS shows a health permission dialog with a list of requested data types. If the user sees this dialog before understanding why the app needs it, they deny all permissions. Once denied, the user must go to iOS Settings → Health → your app to grant permissions — most users never do this. Fix: Create an explanation page that appears before the permission dialog. Explain specifically what data is requested and how it benefits the user. Only call Health.requestAuthorization after the user taps an affirmative button on the explanation screen. Track whether the user has seen the explanation in SharedPreferences and only show it once.
- **Calling Health.getHealthDataFromTypes without calling Health.removeDuplicates on the result** — Apple Health stores data from multiple sources (Apple Watch, iPhone, third-party apps like Strava). When you query steps, you often get duplicate readings of the same activity from multiple sources. Without deduplication, your step count can be 2-5x higher than the actual value. Fix: Always call Health.removeDuplicates(dataPoints) before processing the result from getHealthDataFromTypes. This is a static method on the Health class and correctly deduplicates overlapping time ranges from multiple sources.
- **Testing health data integration in the FlutterFlow web emulator or iOS Simulator without real health data** — Apple HealthKit does not work in the iOS Simulator — it always returns empty data. The FlutterFlow web preview has no access to device health APIs at all. Developers testing in these environments see empty results and assume the integration is broken. Fix: Always test health data integration on a physical iPhone or Android device with health data populated. On iPhone, the Health app shows your data. Use the Health app to manually add test data points if you do not have a wearable connected.

## Best practices

- Request only the specific HealthDataType values your app actually needs — fewer permissions means higher user acceptance rates and a less intimidating permission dialog
- Always call Health.removeDuplicates on the returned data points before summing or averaging — duplicate readings from multiple wearable sources inflate step counts and heart rates
- Store the last successful health data sync timestamp in App State and skip re-fetching if less than 5 minutes have passed — prevents redundant reads when users navigate between pages
- Add a refresh button on the health dashboard for users to manually sync instead of relying only on on-page-load fetching — users expect to control when health data updates
- Show a clear empty state with setup instructions when health permissions are not granted — explain exactly where to go in iOS Settings or Google Health Connect to grant access
- Use the ACTIVE_ENERGY_BURNED data type instead of TOTAL_CALORIES_BURNED for more accurate calorie data, as total calories includes basal metabolic rate which is estimated differently across devices

## Frequently asked questions

### Does the health package work with Fitbit, Garmin, and other wearables in FlutterFlow?

Yes, indirectly. The health package reads from Apple Health (iOS) and Google Health Connect (Android), which act as aggregators for all connected wearables. If the user has synced their Fitbit, Garmin, WHOOP, or Oura Ring with the Apple Health or Google Health Connect app, that data is available through the health package. You do not need separate integrations for each wearable brand.

### Why does the health package return empty data even after permissions are granted?

The most common cause is testing in the iOS Simulator or FlutterFlow web preview — both return empty health data. Test on a physical device. If testing on a physical device and still getting empty results, check two things: (1) the user's Health app actually has data for the requested types and date range, and (2) your app has been granted permission in iOS Settings → Privacy → Health → your app for the specific data types you requested.

### Can I write health data (add workouts) back to Apple Health from FlutterFlow?

Yes. The health package supports both reads and writes. To write data, call health.writeHealthData with a HealthDataType, a start and end DateTime, and a numeric value. For example, to log a workout: health.writeWorkoutData with workoutActivityType, startDate, endDate, and totalEnergyBurned. You need to request write authorization when calling requestAuthorization — add the same HealthDataType to the list with a write permission.

### How do I display a 7-day step count bar chart in FlutterFlow?

Fetch 7 days of STEPS data from the health package, aggregate by day (sum all step data points per calendar day), store as a list of {date, steps} in App State. Create a Custom Widget using fl_chart's BarChart widget. Pass the list as a parameter to the Custom Widget. Map each item to a BarChartGroupData with x as day index (0-6) and y as steps count. Add a horizontal line at 10,000 steps as the goal line. Add the fl_chart package to Pubspec Dependencies if not already present.

### What is the difference between reading from Apple Health and building a direct Apple Watch app?

Reading from Apple Health (using the health Flutter package) only gives you aggregated health data that the Apple Watch has already synced to the iPhone's Health app. There is typically a sync delay of seconds to minutes. A direct Apple Watch app (WatchKit) runs native code on the Apple Watch and can access real-time sensor data (live heart rate, accelerometer) directly. Building a WatchKit extension requires Xcode and native Swift development — it cannot be done in FlutterFlow's visual builder.

### Can RapidDev help build a production health app with wearable integration in FlutterFlow?

Yes. Full health apps with daily goals, streaks, personalized recommendations, push notification reminders, and trend analysis require careful handling of permissions, background sync, and health data schema design. RapidDev has built health tracking apps combining the health Flutter package, Cloud Functions for server-side analytics, and FlutterFlow dashboards for several wellness startups.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-wearable-tech-data-into-a-flutterflow-health-app
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-integrate-wearable-tech-data-into-a-flutterflow-health-app
