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.
Prerequisites
- 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
Add the health package and configure platform permissions
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.
Create the permission request flow with explanation screen
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.
Create the fetchHealthData Custom Action
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.
1import 'package:health/health.dart';23Future<Map<String, dynamic>> fetchHealthData(String dateRange) async {4 final health = Health();5 final now = DateTime.now();6 final days = dateRange == '30days' ? 30 : dateRange == '7days' ? 7 : 1;7 final startDate = DateTime(now.year, now.month, now.day)8 .subtract(Duration(days: days - 1));910 final types = [11 HealthDataType.STEPS,12 HealthDataType.HEART_RATE,13 HealthDataType.SLEEP_ASLEEP,14 HealthDataType.WORKOUT,15 ];1617 bool authorized = await health.requestAuthorization(types);18 if (!authorized) return {'error': 'Not authorized'};1920 final dataPoints = await health.getHealthDataFromTypes(21 startTime: startDate,22 endTime: now,23 types: types,24 );2526 int totalSteps = 0;27 List<double> heartRates = [];28 double sleepHours = 0;29 int workouts = 0;3031 for (final point in dataPoints) {32 final value = (point.value as NumericHealthValue).numericValue;33 switch (point.type) {34 case HealthDataType.STEPS:35 totalSteps += value.toInt();36 break;37 case HealthDataType.HEART_RATE:38 heartRates.add(value.toDouble());39 break;40 case HealthDataType.SLEEP_ASLEEP:41 sleepHours += point.dateTo.difference(point.dateFrom).inMinutes / 60;42 break;43 case HealthDataType.WORKOUT:44 workouts++;45 break;46 default:47 break;48 }49 }5051 return {52 'steps': totalSteps,53 'avgHeartRate': heartRates.isEmpty ? 0 :54 (heartRates.reduce((a, b) => a + b) / heartRates.length).round(),55 'sleepHours': double.parse(sleepHours.toStringAsFixed(1)),56 'workouts': workouts,57 'fetchedAt': now.toIso8601String(),58 };59}Expected result: Custom Action returns a JSON object with steps, average heart rate, sleep hours, and workout count for the specified date range.
Build the health dashboard with KPI cards and trend display
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.
Sync health data to Firestore for persistence and analytics
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 working example
1// Custom Action: fetchHealthData2// Reads from Apple Health (iOS) or Google Health Connect (Android)3// Returns aggregated daily health metrics45import 'package:health/health.dart';6import 'package:cloud_firestore/cloud_firestore.dart';7import 'package:firebase_auth/firebase_auth.dart';89Future<Map<String, dynamic>> fetchHealthData(String dateRange) async {10 final health = Health();11 final now = DateTime.now();12 final days = dateRange == '30days' ? 30 : dateRange == '7days' ? 7 : 1;13 final startDate = DateTime(now.year, now.month, now.day)14 .subtract(Duration(days: days - 1));1516 final types = [17 HealthDataType.STEPS,18 HealthDataType.HEART_RATE,19 HealthDataType.SLEEP_ASLEEP,20 HealthDataType.ACTIVE_ENERGY_BURNED,21 ];2223 bool authorized = await health.requestAuthorization(types);24 if (!authorized) return {'error': 'not_authorized', 'steps': 0};2526 final dataPoints = await health.getHealthDataFromTypes(27 startTime: startDate,28 endTime: now,29 types: types,30 );3132 int totalSteps = 0;33 List<double> heartRates = [];34 double sleepHours = 0;35 double caloriesBurned = 0;3637 for (final point in Health.removeDuplicates(dataPoints)) {38 final value = (point.value as NumericHealthValue).numericValue;39 switch (point.type) {40 case HealthDataType.STEPS:41 totalSteps += value.toInt();42 break;43 case HealthDataType.HEART_RATE:44 heartRates.add(value.toDouble());45 break;46 case HealthDataType.SLEEP_ASLEEP:47 sleepHours += point.dateTo.difference(point.dateFrom).inMinutes / 60;48 break;49 case HealthDataType.ACTIVE_ENERGY_BURNED:50 caloriesBurned += value.toDouble();51 break;52 default:53 break;54 }55 }5657 final result = {58 'steps': totalSteps,59 'avgHeartRate': heartRates.isEmpty ? 0 :60 (heartRates.reduce((a, b) => a + b) / heartRates.length).round(),61 'sleepHours': double.parse(sleepHours.toStringAsFixed(1)),62 'calories': caloriesBurned.round(),63 'fetchedAt': now.toIso8601String(),64 };6566 // Persist to Firestore for history and cross-device access67 final uid = FirebaseAuth.instance.currentUser?.uid;68 if (uid != null && dateRange == 'today') {69 final today = '${now.year}-${now.month.toString().padLeft(2,'0')}-${now.day.toString().padLeft(2,'0')}';70 await FirebaseFirestore.instance71 .collection('health_data')72 .doc('${uid}_$today')73 .set({...result, 'userId': uid, 'date': today}, SetOptions(merge: true));74 }7576 return result;77}Common mistakes
Why it's a problem: Requesting health data permissions immediately on app launch without showing an explanation screen first
How to avoid: 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.
Why it's a problem: Calling Health.getHealthDataFromTypes without calling Health.removeDuplicates on the result
How to avoid: 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.
Why it's a problem: Testing health data integration in the FlutterFlow web emulator or iOS Simulator without real health data
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a FlutterFlow health app and want to read data from Apple Watch and Fitbit (via Apple Health/Google Fit). Write me: (1) A Flutter Custom Action using the health package that fetches steps, heart rate, and sleep data for the last 7 days, (2) How to properly request health permissions with an explanation screen first, (3) How to deduplicate health data from multiple sources, and (4) How to store daily health summaries in Firestore for historical trend analysis.
Create a health dashboard page that shows four KPI cards: total steps today, average heart rate, last night's sleep hours, and weekly workouts count. Fetch the data on page load using a Custom Action that calls the health package. Add ChoiceChips for Today / 7 Days / 30 Days that update all four metrics when selected.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation