READ Permission
Allows your app to view existing health data
Before your app can read or write health data, users must grant permission for each specific data type. Think of it like giving your app a key to access certain parts of the health data vault.
READ Permission
Allows your app to view existing health data
WRITE Permission
Allows your app to add new health data
Privacy-First Approach
Explicit Permission System
Request Permission
Ask the user for permission to access health data
final types = [HealthDataType.STEPS];final granted = await health.requestAuthorization(types);Check Result
See if the user granted permission
if (granted) { print('✓ Permission granted!'); // Now you can read data} else { print('✗ Permission denied');}Read or Write Data
Use the health data
final data = await health.getHealthDataFromTypes( types: [HealthDataType.STEPS], startDate: yesterday, endDate: now,);Example:
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'); }}Check if your app already has permission before requesting or reading 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);}// Check multiple data typesfinal types = [ HealthDataType.STEPS, HealthDataType.HEART_RATE, HealthDataType.WEIGHT,];
final hasPermission = await health.hasPermissions(types);The return values are as follow:
| Value | Meaning |
|---|---|
true |
✓ All permissions granted |
false |
✗ At least one permission denied or not granted |
null |
❓ Cannot determine (iOS READ permissions) |
Ask users to grant permission for specific health data types.
Choose Data Types
Decide what health data you need
final types = [ HealthDataType.STEPS, HealthDataType.HEART_RATE,];Request Permission
Show the system permission dialog
final granted = await health.requestAuthorization(types);Handle Response
Check if permission was granted
if (granted) { // Start reading data await fetchHealthData();} else { // Show error message showPermissionError();}Control exactly what access you need for each data type:
final types = [ HealthDataType.STEPS, // Just read HealthDataType.WEIGHT, // Read and write HealthDataType.WORKOUT, // Read and write];
final permissions = [ HealthDataAccess.READ, // Steps: read only HealthDataAccess.READ_WRITE, // Weight: read and write HealthDataAccess.READ_WRITE, // Workout: read and write];
final granted = await health.requestAuthorization( types, permissions: permissions,);| Access Level | Can Read | Can Write |
|---|---|---|
READ |
✓ | ✗ |
WRITE |
✗ | ✓ |
READ_WRITE |
✓ | ✓ |
Future<bool> requestAuthorization( List<HealthDataType> types, { List<HealthDataAccess>? permissions,})| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
types |
List<HealthDataType> |
Yes | - | Data types to request |
permissions |
List<HealthDataAccess>? |
No | All READ | Access level for each type |
true: Authorization succeeded (iOS: dialog shown, Android: granted)false: Authorization failed or deniedAsk for permissions when the user first uses a health feature:
Future<void> initializeHealth() async { // Check if we've asked before final prefs = await SharedPreferences.getInstance(); final askedBefore = prefs.getBool('health_permission_asked') ?? false;
if (!askedBefore) { await health.requestAuthorization([HealthDataType.STEPS]); await prefs.setBool('health_permission_asked', true); }}Always check and request before trying to read data:
Future<List<HealthDataPoint>> getStepsData() async { // Check permission first final hasPermission = await health.hasPermissions( [HealthDataType.STEPS], );
if (hasPermission != true) { // Request if not granted final granted = await health.requestAuthorization( [HealthDataType.STEPS], );
if (!granted) { throw Exception('Permission denied'); } }
// Now safe to read return await health.getHealthDataFromTypes(...);}Request only what you need, when you need 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],);Some iOS health data can only be read, not written by apps:
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(),);On Android, BMI requires weight and height data:
final types = [HealthDataType.BODY_MASS_INDEX];
// Android automatically adds WEIGHT and HEIGHT to the requestawait health.requestAuthorization(types);Track permission state in your app:
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); } }}Revoke all Health Connect permissions on Android. The app must be restarted for changes to take effect.
Future<void> revokePermissions()// Revoke all permissions (Android only)await health.revokePermissions();if (Platform.isAndroid) { await health.revokePermissions();
// Inform user to restart showDialog( context: context, builder: (context) => AlertDialog( title: Text('Permissions Revoked'), content: Text('Please restart the app for changes to take effect.'), actions: [ TextButton( onPressed: () => SystemNavigator.pop(), child: Text('Exit App'), ), ], ), );}Required for: STEPS, DISTANCE, WORKOUT data
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]);Required for: WORKOUT with distance tracking
Future<bool> requestLocationPermission() async { final status = await Permission.location.request(); return status.isGranted;}
// Use before requesting workout dataawait requestLocationPermission();await health.requestAuthorization([HealthDataType.WORKOUT]);Future<bool> setupHealthPermissions() async { // 1. Check Health Connect availability final available = await health.isHealthConnectAvailable(); if (!available) { await health.installHealthConnect(); return false; }
// 2. Request runtime permissions first await Permission.activityRecognition.request(); await Permission.location.request();
// 3. Request health data permissions final types = [ HealthDataType.STEPS, HealthDataType.WORKOUT, ];
final granted = await health.requestAuthorization(types); return granted;}Problem: User denied permissions and they can’t be requested again.
Solution: Guide users to Settings:
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'), ), ], ),);Problem: Health Connect returns unavailable status.
Solution:
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;}Problem: Permission check returns null on iOS.
Solution: This is expected behavior for privacy. Always request permissions:
final hasPermission = await health.hasPermissions(types);
// On iOS, hasPermission will be null for READif (hasPermission != true) { // Request regardless await health.requestAuthorization(types);}final types = [HealthDataType.BODY_MASS_INDEX];
// Android automatically adds WEIGHT and HEIGHT to the requestawait health.requestAuthorization(types);try { bool authorized = await health.requestAuthorization(types, permissions: perms);
if (!authorized) { // User denied or error occurred showDialog( context: context, builder: (context) => AlertDialog( title: Text('Permissions Required'), content: Text('Please grant health data permissions to continue.'), ), ); }} catch (e) { print('Error requesting authorization: $e');}Revoke all Health Connect permissions. Android only - no effect on iOS.
Future<void> revokePermissions()if (Platform.isAndroid) { await health.revokePermissions();
// Inform user to restart app showDialog( context: context, builder: (context) => AlertDialog( title: Text('Permissions Revoked'), content: Text('Please restart the app for changes to take effect.'), actions: [ TextButton( onPressed: () => SystemNavigator.pop(), child: Text('Exit App'), ), ], ), );}For Android, certain health data requires additional runtime permissions:
Required for steps, distance, and workout data:
import 'package:permission_handler/permission_handler.dart';
// Request before requesting health authorizationawait Permission.activityRecognition.request();
// Then request health permissionsawait health.requestAuthorization([HealthDataType.STEPS]);Required for workouts with distance information:
await Permission.location.request();await Permission.locationWhenInUse.request();
// Then request workout permissionsawait health.requestAuthorization([HealthDataType.WORKOUT]);Future<bool> setupAndroidPermissions() async { // 1. Check Health Connect availability final available = await health.isHealthConnectAvailable(); if (!available) { await health.installHealthConnect(); return false; }
// 2. Request runtime permissions await Permission.activityRecognition.request(); await Permission.location.request();
// 3. Request health data permissions final types = [ HealthDataType.STEPS, HealthDataType.WORKOUT, HealthDataType.HEART_RATE, ];
return await health.requestAuthorization(types);}Future<void> initializeHealth() async { await health.configure();
final types = [ HealthDataType.STEPS, HealthDataType.HEART_RATE, HealthDataType.WEIGHT, ];
// Check existing permissions bool? hasPerms = await health.hasPermissions(types);
if (hasPerms != true) { // Request permissions bool authorized = await health.requestAuthorization(types);
if (!authorized) { // Show explanation to user _showPermissionRationale(); } }}Request permissions only when needed:
Future<List<HealthDataPoint>> fetchSteps() async { final type = HealthDataType.STEPS;
// Check permission first bool? hasPerm = await health.hasPermissions([type]);
if (hasPerm != true) { // Request on-demand bool authorized = await health.requestAuthorization([type]); if (!authorized) { throw Exception('Permission denied'); } }
// Now fetch data return await health.getHealthDataFromTypes( types: [type], startTime: DateTime.now().subtract(Duration(days: 1)), endTime: DateTime.now(), );}Future<void> handlePermissionDenied() async { // On Android, direct user to Health Connect settings if (Platform.isAndroid) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Permissions Required'), content: Text( 'Please grant permissions in Health Connect settings.', ), actions: [ TextButton( onPressed: () async { // Open Health Connect await health.installHealthConnect(); }, child: Text('Open Settings'), ), ], ), ); }
// On iOS, direct to Settings app if (Platform.isIOS) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Permissions Required'), content: Text( 'Please enable Health permissions in Settings > Health > Data Access & Devices.', ), actions: [ TextButton( onPressed: () => openAppSettings(), child: Text('Open Settings'), ), ], ), ); }}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,]);class HealthPermissionManager { final Health health; Map<HealthDataType, HealthDataAccess> _grantedPermissions = {};
Future<void> refreshPermissions(List<HealthDataType> types) async { for (var type in types) { // Check READ bool? hasRead = await health.hasPermissions([type]);
// Check WRITE (Android only) bool? hasWrite = await health.hasPermissions( [type], permissions: [HealthDataAccess.WRITE], );
if (hasWrite == true) { _grantedPermissions[type] = HealthDataAccess.READ_WRITE; } else if (hasRead == true) { _grantedPermissions[type] = HealthDataAccess.READ; } } }
bool canRead(HealthDataType type) { return _grantedPermissions[type] != null; }
bool canWrite(HealthDataType type) { return _grantedPermissions[type] == HealthDataAccess.READ_WRITE; }}