Request, check and revoke health data permissions on iOS and Android, including the Android runtime permissions.
Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor
Health data is sensitive. Both iOS and Android require explicit user consent before your app can read or write health data, and the user grants that consent separately for each data type.
Each type is requested with one of these access levels (HealthDataAccess):
By default, requestAuthorization() requests READ access.
Choose the data types
final types = [ HealthDataType.STEPS, HealthDataType.HEART_RATE,];
Show the system permission dialog
final granted = await health.requestAuthorization(types);
Handle the response
if (granted) { // Start reading data await fetchHealthData();} else { // Show error message showPermissionError();}
The same flow as a complete function:
import 'package:health/health.dart';Future<void> requestStepsPermission() async { final health = Health(); await health.configure(); // Request permission to read steps final types = [HealthDataType.STEPS]; final granted = await health.requestAuthorization(types); if (granted) { print('✓ Can now read step data!'); } else { print('✗ User denied permission'); }}
hasPermissions() checks whether your app already has permission, before you request or read data:
// Check if we can read stepsfinal types = [HealthDataType.STEPS];final hasPermission = await health.hasPermissions(types);if (hasPermission == true) { print('✓ We have permission!');} else { print('Need to request permission'); await health.requestAuthorization(types);}
Android requires additional runtime permissions for certain health data types. These are separate from the Health Connect permissions. Request them with the permission_handler package before you request health authorization.
import 'package:permission_handler/permission_handler.dart';Future<bool> requestActivityRecognition() async { final status = await Permission.activityRecognition.request(); return status.isGranted;}// Use before requesting health dataawait requestActivityRecognition();await health.requestAuthorization([HealthDataType.STEPS]);
Future<bool> requestLocationPermission() async { final status = await Permission.location.request(); return status.isGranted;}// Use before requesting workout dataawait requestLocationPermission();await health.requestAuthorization([HealthDataType.WORKOUT]);
To request locationWhenInUse as well:
await Permission.location.request();await Permission.locationWhenInUse.request();// Then request workout permissionsawait health.requestAuthorization([HealthDataType.WORKOUT]);
Apps can read, but not write, some iOS health data. Always request READ for these types:
final readOnlyTypes = [ HealthDataType.ELECTROCARDIOGRAM, // Apple Watch only HealthDataType.HIGH_HEART_RATE_EVENT, // Apple Watch only HealthDataType.LOW_HEART_RATE_EVENT, // Apple Watch only HealthDataType.IRREGULAR_HEART_RATE_EVENT, // Apple Watch only HealthDataType.WALKING_HEART_RATE, // Calculated by iOS HealthDataType.GENDER, // User profile data HealthDataType.BLOOD_TYPE, // User profile data HealthDataType.BIRTH_DATE, // User profile data];// Always use READ for these typesawait health.requestAuthorization( readOnlyTypes, permissions: readOnlyTypes.map((t) => HealthDataAccess.READ).toList(),);
Warning
Requesting WRITE access for read-only types throws an ArgumentError.
On Android, BMI requires weight and height data. When you request BODY_MASS_INDEX, Android automatically includes the WEIGHT and HEIGHT permissions in the request:
final types = [HealthDataType.BODY_MASS_INDEX];// Android automatically adds WEIGHT and HEIGHT to the requestawait health.requestAuthorization(types);
Request only what you need, when the user needs it:
// Request steps for main screenawait health.requestAuthorization([HealthDataType.STEPS]);// Later, when user opens workout featureawait health.requestAuthorization([HealthDataType.WORKOUT]);// When user wants to log weightawait health.requestAuthorization( [HealthDataType.WEIGHT], permissions: [HealthDataAccess.READ_WRITE],);
Or request permissions in stages:
// Stage 1: Basic metricsawait health.requestAuthorization([ HealthDataType.STEPS, HealthDataType.DISTANCE_WALKING_RUNNING,]);// Stage 2: Add workouts after user engagementawait health.requestAuthorization([ HealthDataType.WORKOUT, HealthDataType.ACTIVE_ENERGY_BURNED,]);// Stage 3: Add biometrics for premium featuresawait health.requestAuthorization([ HealthDataType.HEART_RATE, HealthDataType.BLOOD_PRESSURE_SYSTOLIC, HealthDataType.BLOOD_PRESSURE_DIASTOLIC,]);
Tip
Users are more likely to grant permissions if you explain why you need them first.
Check each type and request only the missing ones:
class PermissionManager { final Health health; PermissionManager(this.health); Future<Map<HealthDataType, bool?>> checkAllPermissions( List<HealthDataType> types, ) async { final results = <HealthDataType, bool?>{}; for (final type in types) { final hasPermission = await health.hasPermissions([type]); results[type] = hasPermission; } return results; } Future<void> requestMissingPermissions( List<HealthDataType> types, ) async { final missingTypes = <HealthDataType>[]; for (final type in types) { final hasPermission = await health.hasPermissions([type]); if (hasPermission != true) { missingTypes.add(type); } } if (missingTypes.isNotEmpty) { await health.requestAuthorization(missingTypes); } }}
Track READ and WRITE access per type. Pass permissions to hasPermissions() to check a specific access level:
Problem: the user denied permissions, and they cannot be requested again.
Solution: guide the user to the Settings app:
showDialog( context: context, builder: (context) => AlertDialog( title: Text('Enable Health Access'), content: Text( '1. Open Settings app\n' '2. Scroll to ${yourAppName}\n' '3. Tap Health\n' '4. Enable the data types you want to share' ), actions: [ TextButton( onPressed: () => OpenSettings.openSettings(), child: Text('Open Settings'), ), ], ),);
Health Connect not available
Problem: Health Connect returns an unavailable status.
Solution: check the SDK status and act on it:
final status = await health.getHealthConnectSdkStatus();switch (status) { case HealthConnectSdkStatus.SDK_UNAVAILABLE: // Not installed - offer to install await health.installHealthConnect(); break; case HealthConnectSdkStatus.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED: // Needs update showUpdateDialog(); break; case HealthConnectSdkStatus.SDK_AVAILABLE: // All good! break;}
hasPermissions returns null
Problem: the permission check returns null on iOS.
Solution: this is expected behavior for privacy. Always request the permissions:
final hasPermission = await health.hasPermissions(types);// On iOS, hasPermission will be null for READif (hasPermission != true) { // Request regardless await health.requestAuthorization(types);}