Health data is sensitive. Both iOS and Android require explicit user consent before your app can access or write health information.
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

Platform Differences

  • iOS (HealthKit)
  • Android (Health Connect)
Privacy-First Approach
For privacy, iOS won’t tell you if READ permission was granted. The hasPermissions() method returns null for read checks.
  • Users can grant or deny each data type individually
  • Once denied, users must change it in Settings app
  • Your app never knows if READ was granted (privacy protection)
  • WRITE permissions can be verified

Basic Permission Flow

1

Request Permission

Ask the user for permission to access health data
final types = [HealthDataType.STEPS];
final granted = await health.requestAuthorization(types);
2

Check Result

See if the user granted permission
if (granted) {
  print('✓ Permission granted!');
  // Now you can read data
} else {
  print('✗ Permission denied');
}
3

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');
  }
}
Start with just one or two data types (like STEPS) to keep things simple when learning.

Checking Permissions

hasPermissions()

Check if your app already has permission before requesting or reading data.
// Check if we can read steps
final 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);
}
The return values are as follow:
ValueMeaning
true✓ All permissions granted
false✗ At least one permission denied or not granted
null❓ Cannot determine (iOS READ permissions)
On iOS, checking READ permissions always returns null due to privacy. Always assume you need to request if you get null.

Requesting Permissions

requestAuthorization()

Ask users to grant permission for specific health data types.
  • Beginner
  • Advanced
1

Choose Data Types

Decide what health data you need
final types = [
  HealthDataType.STEPS,
  HealthDataType.HEART_RATE,
];
2

Request Permission

Show the system permission dialog
final granted = await health.requestAuthorization(types);
3

Handle Response

Check if permission was granted
if (granted) {
  // Start reading data
  await fetchHealthData();
} else {
  // Show error message
  showPermissionError();
}
By default, requestAuthorization() asks for READ permission. This is perfect for most apps that just want to display health data.

Method Signature

Future<bool> requestAuthorization(
  List<HealthDataType> types, {
  List<HealthDataAccess>? permissions,
})

Parameters

ParameterTypeRequiredDefaultDescription
typesList<HealthDataType>Yes-Data types to request
permissionsList<HealthDataAccess>?NoAll READAccess level for each type

Returns

  • true: Authorization succeeded (iOS: dialog shown, Android: granted)
  • false: Authorization failed or denied

Common Permission Patterns

Ask 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 screen
await health.requestAuthorization([HealthDataType.STEPS]);

// Later, when user opens workout feature
await health.requestAuthorization([HealthDataType.WORKOUT]);

// When user wants to log weight
await health.requestAuthorization(
  [HealthDataType.WEIGHT],
  permissions: [HealthDataAccess.READ_WRITE],
);
Users are more likely to grant permissions if you explain why you need them first!

Advanced Topics

  • Read-Only Types (iOS)
  • BMI Handling (Android)
  • Permission State Tracking
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 types
await health.requestAuthorization(
  readOnlyTypes,
  permissions: readOnlyTypes.map((t) => HealthDataAccess.READ).toList(),
);
Requesting WRITE access for read-only types will throw an ArgumentError.

Revoking Permissions

revokePermissions()

Android Only - This method has no effect on iOS.
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();
On iOS, users must manually change permissions in the Settings app under your app’s Health section.

Android Runtime Permissions

Android requires additional runtime permissions for certain health data types, separate from Health Connect permissions.

Activity Recognition Permission

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 data
await requestActivityRecognition();
await health.requestAuthorization([HealthDataType.STEPS]);

Location Permission

Required for: WORKOUT with distance tracking
Future<bool> requestLocationPermission() async {
  final status = await Permission.location.request();
  return status.isGranted;
}

// Use before requesting workout data
await requestLocationPermission();
await health.requestAuthorization([HealthDataType.WORKOUT]);

Complete Permission Flow (Android)

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;
}

Troubleshooting

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 READ
if (hasPermission != true) {
  // Request regardless
  await health.requestAuthorization(types);
}

See Also

final types = [HealthDataType.BODY_MASS_INDEX];

// Android automatically adds WEIGHT and HEIGHT to the request
await health.requestAuthorization(types);

Error Handling

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');
}

Revoking Permissions

revokePermissions()

Revoke all Health Connect permissions. Android only - no effect on iOS.
Future<void> revokePermissions()

Platform Support

  • Android: Revokes all Health Connect permissions. App must be completely killed and restarted for changes to take effect.
  • iOS: No-op. iOS doesn’t support programmatic permission revocation.

Example

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'),
        ),
      ],
    ),
  );
}

Android Runtime Permissions

For Android, certain health data requires additional runtime permissions:

Activity Recognition

Required for steps, distance, and workout data:
import 'package:permission_handler/permission_handler.dart';

// Request before requesting health authorization
await Permission.activityRecognition.request();

// Then request health permissions
await health.requestAuthorization([HealthDataType.STEPS]);

Location

Required for workouts with distance information:
await Permission.location.request();
await Permission.locationWhenInUse.request();

// Then request workout permissions
await health.requestAuthorization([HealthDataType.WORKOUT]);

Complete Android Setup

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);
}

Permission Patterns

Initial Setup Flow

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();
    }
  }
}

Lazy Permission Requests

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(),
  );
}

Handling Denied Permissions

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'),
          ),
        ],
      ),
    );
  }
}

Advanced Permission Management

Granular Permission Requests

Request permissions in stages:
// Stage 1: Basic metrics
await health.requestAuthorization([
  HealthDataType.STEPS,
  HealthDataType.DISTANCE_WALKING_RUNNING,
]);

// Stage 2: Add workouts after user engagement
await health.requestAuthorization([
  HealthDataType.WORKOUT,
  HealthDataType.ACTIVE_ENERGY_BURNED,
]);

// Stage 3: Add biometrics for premium features
await health.requestAuthorization([
  HealthDataType.HEART_RATE,
  HealthDataType.BLOOD_PRESSURE_SYSTOLIC,
  HealthDataType.BLOOD_PRESSURE_DIASTOLIC,
]);

Permission State Tracking

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;
  }
}

See Also