On iOS, if a user denies your app permission to read their step count, HealthKit won’t tell you. Your query just comes back empty, exactly as it would for someone who simply hasn’t walked anywhere. That one privacy decision shapes how you build Flutter HealthKit Health Connect support more than any API detail does, because the Android side behaves differently, and your shared Dart layer has to make sense of both.
In Crumb Count, our nutrition app for Indian kitchens, we read activity data from HealthKit and Health Connect through platform channels into Swift and Kotlin, and present it to the rest of the app through a single Dart API. This post covers how that layer is shaped, the permission differences, and the store review rules that can hold up a release.
Flutter HealthKit Health Connect: one Dart API
The feature code in your app should ask simple questions: how many active calories today, what workouts happened this week. It shouldn’t know which platform answered. So the Dart surface stays small:
abstract class HealthSource {
Future<HealthAccess> requestAccess(Set<HealthMetric> metrics);
Future<double?> dailyTotal(HealthMetric metric, DateTime day);
Future<List<Workout>> workouts(DateTime from, DateTime to);
}
Two implementations sit behind it, one per platform, each a MethodChannel into native code. A few rules keep them honest:
- Normalise units at the native edge. HealthKit works with
HKQuantityand explicit units, Health Connect with its own typed records. Convert to one unit per metric (kilocalories, metres, seconds) before anything crosses the channel. - Ask the platform to aggregate. A user with a phone and a watch can have overlapping step samples from both. HealthKit’s statistics queries and Health Connect’s aggregate API both handle overlapping sources for you. Summing raw samples in Dart double-counts, and you’ll find out from a one-star review.
- Return null for “unknown”, not zero. Zero steps is a fact. No data is not a fact. The UI needs to tell them apart.
- Send dates as UTC instants plus the user’s time zone. “Today” is a local-calendar concept, and travel across zones will break any day bucketing that isn’t explicit about it.
You don’t have to write the channels yourself. The health package on pub.dev wraps both platforms and is a reasonable starting point. We went native because we wanted direct control over queries and permission flows, and because the app already had Swift and Kotlin code for widgets and Live Activities. If your app has none, start with the package and drop to channels where it falls short.
Permissions behave differently on each side
HealthKit
- Add the HealthKit capability to the app target, and both Info.plist strings:
NSHealthShareUsageDescriptionfor reading andNSHealthUpdateUsageDescriptionfor writing. Apple wants them separate, and vague text (“to improve your experience”) is a common reason for rejection. - Call
isHealthDataAvailable()before anything else. - As above:
authorizationStatus(for:)only reflects write permission. For reads, Apple’s documentation is explicit that your app can’t tell full access from denied access. Design for it. Show “No activity data yet. You can check Health permissions in Settings” rather than an error.
Health Connect
- On Android 14 and later, Health Connect is part of the platform. On Android 13 and lower, it’s a separate app from Google Play. Call
HealthConnectClient.getSdkStatus()and send users to install or update it when needed. - Declare each permission in the manifest (
android.permission.health.READ_STEPSand so on). Request only what you use. - You must ship a screen that explains your privacy policy, reachable through an activity handling
androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE(Android 13 and lower) and an activity-alias forandroid.intent.action.VIEW_PERMISSION_USAGE(Android 14 and later). It has to show the same privacy policy you registered in Play Console. - By default you can read data from up to 30 days before the permission was granted. Older history needs
READ_HEALTH_DATA_HISTORY. Background reads need their own permission and aren’t available on Android 13 and lower at all.
Unlike HealthKit, Health Connect does tell you which permissions were granted, so the Android implementation can report denial accurately. Your shared HealthAccess type should allow “unknown” as a state so the iOS implementation isn’t forced to lie.
Store review gotchas for HealthKit and Health Connect apps
This is where releases slip. From Apple’s App Review Guidelines:
- Guideline 5.1.3(i): data from HealthKit can’t be used or disclosed for advertising, marketing or use-based data mining. If your analytics SDK sends events that include health values, that’s a problem. Audit what leaves the device.
- Guideline 5.1.3(ii): you can’t write false or inaccurate data into HealthKit, and you can’t store personal health information in iCloud. Check any backup or sync feature against this.
- Guideline 2.5.1: HealthKit should be used for health and fitness purposes and integrate with the Health app, and the integration should be mentioned in your app description. Reviewers do check.
On Google Play, you declare in Play Console every Health Connect data type you read or write, and each update must list existing types as well as new ones. Asking for a type the app doesn’t visibly use is a quick way to get the declaration rejected. Treat the manifest permission list and the Play declaration as one artefact that changes together.
One more thing reviewers and users both notice: numbers you calculate yourself. In Crumb Count, every calorie-burn figure cites the published model it comes from. That’s good science, and it also gives you a straight answer when someone asks why your number differs from their watch.
Testing
Simulators get you partway. The iOS Simulator has a Health app you can add samples to, and an Android emulator with Health Connect installed lets you insert records. But overlapping sources, permission revocation from Settings and time-zone travel need real devices and a written test script. Run it before each release that touches health code.
orithLabs builds this kind of native integration into Flutter apps, and the same thinking applies to a React Native app with native modules. If you’re planning health data features, our mobile development page explains how we work, and the Crumb Count case study shows it shipped.