Skip to content

Permissions

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

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

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:

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.

  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();
    }
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 denied
Request on First Launch

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);
}
}
Request Before Reading

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

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

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

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

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

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]);
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;
}
Permissions Denied on iOS

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'),
),
],
),
);
Health Connect Not Available

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;
}
hasPermissions Returns Null

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);
}
final types = [HealthDataType.BODY_MASS_INDEX];
// Android automatically adds WEIGHT and HEIGHT to the request
await 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()
  • 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.
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 authorization
await Permission.activityRecognition.request();
// Then request health permissions
await health.requestAuthorization([HealthDataType.STEPS]);

Required for workouts with distance information:

await Permission.location.request();
await Permission.locationWhenInUse.request();
// Then request workout permissions
await 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 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,
]);
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;
}
}