Flutter健康数据访问插件health的使用
Flutter健康数据访问插件health的使用
插件简介
Health
插件允许从 Apple Health 和 Google Health Connect 读取和写入健康数据。请注意,自2024年5月1日起,Google已停止对Google Fit API的支持,因此该插件自11.0.0版本起移除了对Google Fit的支持。
支持的功能
- 权限管理:通过
hasPermissions
、requestAuthorization
、revokePermissions
方法处理健康数据访问权限。 - 读取健康数据:使用
getHealthDataFromTypes
方法读取数据。 - 写入健康数据:使用
writeHealthData
方法写入数据。 - 写入锻炼数据:使用
writeWorkout
方法写入锻炼记录。 - 写入膳食数据:在iOS和Android上使用
writeMeal
方法写入膳食信息。 - 写入听力图(iOS):使用
writeAudiogram
方法写入听力图。 - 写入血压数据:使用
writeBloodPressure
方法写入血压信息。 - 获取步数统计:使用
getTotalStepsInInterval
方法获取步数。 - 清理重复数据点:通过
removeDuplicates
方法清理重复数据。 - 删除特定时间段的数据:使用
delete
方法删除指定类型的数据。
对于Android设备,目标手机需要安装Health Connect应用,并且能够连接互联网。
设置
Apple Health (iOS)
-
在
Info.plist
中添加以下两项:<key>NSHealthShareUsageDescription</key> <string>我们将同步您的数据到Apple Health应用程序,以提供更好的见解</string> <key>NSHealthUpdateUsageDescription</key> <string>我们将同步您的数据到Apple Health应用程序,以提供更好的见解</string>
-
打开Flutter项目,在Xcode中为Runner target启用“HealthKit”功能。
Google Health Connect (Android)
-
在
AndroidManifest.xml
中添加以下代码段,以检查是否安装了Health Connect应用:<queries> <package android:name="com.google.android.apps.healthdata" /> <intent> <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" /> </intent> </queries>
-
添加隐私政策链接(可选但推荐):
<activity-alias android:name="ViewPermissionUsageActivity" android:exported="true" android:targetActivity=".MainActivity" android:permission="android.permission.START_VIEW_PERMISSION_USAGE"> <intent-filter> <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" /> <category android:name="android.intent.category.HEALTH_PERMISSIONS" /> </intent-filter> </activity-alias>
-
对于每个要访问的数据类型,在
AndroidManifest.xml
中添加相应的读写权限。例如,访问心率数据:<uses-permission android:name="android.permission.health.READ_HEART_RATE"/> <uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
-
如果需要访问健身数据(如步数),需添加活动识别API权限:
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
-
如果请求锻炼距离,则需添加位置权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
使用
permission_handler
插件请求上述权限:await Permission.activityRecognition.request(); await Permission.location.request();
-
添加意图过滤器以显示Health Connect权限界面:
<activity android:name=".MainActivity" android:exported="true"> ... <intent-filter> <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" /> </intent-filter> </activity>
-
对于Android 14,确保主活动继承自
FlutterFragmentActivity
:import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity : FlutterFragmentActivity() { ... }
-
确保
android/gradle.properties
文件包含以下内容:org.gradle.jvmargs=-Xmx1536M android.enableJetifier=true android.useAndroidX=true
使用示例
以下是一个完整的Flutter应用程序示例,演示如何配置和使用Health
插件。
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:health/health.dart';
import 'package:permission_handler/permission_handler.dart';
// 全局Health实例
final health = Health();
void main() => runApp(HealthApp());
class HealthApp extends StatefulWidget {
const HealthApp({super.key});
@override
HealthAppState createState() => HealthAppState();
}
enum AppState {
DATA_NOT_FETCHED,
FETCHING_DATA,
DATA_READY,
NO_DATA,
AUTHORIZED,
AUTH_NOT_GRANTED,
DATA_ADDED,
DATA_DELETED,
DATA_NOT_ADDED,
DATA_NOT_DELETED,
STEPS_READY,
HEALTH_CONNECT_STATUS,
PERMISSIONS_REVOKING,
PERMISSIONS_REVOKED,
PERMISSIONS_NOT_REVOKED,
}
class HealthAppState extends State<HealthApp> {
List<HealthDataPoint> _healthDataList = [];
AppState _state = AppState.DATA_NOT_FETCHED;
int _nofSteps = 0;
List<RecordingMethod> recordingMethodsToFilter = [];
// 所有平台支持的数据类型
List<HealthDataType> get types => (Platform.isAndroid)
? dataTypesAndroid
: (Platform.isIOS)
? dataTypesIOS
: [];
// 权限设置
List<HealthDataAccess> get permissions => types
.map((type) =>
[
HealthDataType.WALKING_HEART_RATE,
HealthDataType.ELECTROCARDIOGRAM,
HealthDataType.HIGH_HEART_RATE_EVENT,
HealthDataType.LOW_HEART_RATE_EVENT,
HealthDataType.IRREGULAR_HEART_RATE_EVENT,
HealthDataType.EXERCISE_TIME,
].contains(type)
? HealthDataAccess.READ
: HealthDataAccess.READ_WRITE)
.toList();
@override
void initState() {
health.configure();
health.getHealthConnectSdkStatus();
super.initState();
}
/// 安装Health Connect应用
Future<void> installHealthConnect() async =>
await health.installHealthConnect();
/// 授权访问健康数据
Future<void> authorize() async {
await Permission.activityRecognition.request();
await Permission.location.request();
bool? hasPermissions =
await health.hasPermissions(types, permissions: permissions);
hasPermissions = false;
bool authorized = false;
if (!hasPermissions) {
try {
authorized =
await health.requestAuthorization(types, permissions: permissions);
} catch (error) {
debugPrint("Exception in authorize: $error");
}
}
setState(() =>
_state = (authorized) ? AppState.AUTHORIZED : AppState.AUTH_NOT_GRANTED);
}
/// 获取Health Connect状态
Future<void> getHealthConnectSdkStatus() async {
assert(Platform.isAndroid, "This is only available on Android");
final status = await health.getHealthConnectSdkStatus();
setState(() {
_contentHealthConnectStatus =
Text('Health Connect Status: ${status?.name.toUpperCase()}');
_state = AppState.HEALTH_CONNECT_STATUS;
});
}
/// 获取健康数据
Future<void> fetchData() async {
setState(() => _state = AppState.FETCHING_DATA);
final now = DateTime.now();
final yesterday = now.subtract(const Duration(hours: 24));
_healthDataList.clear();
try {
List<HealthDataPoint> healthData = await health.getHealthDataFromTypes(
types: types,
startTime: yesterday,
endTime: now,
recordingMethodsToFilter: recordingMethodsToFilter,
);
healthData.sort((a, b) => b.dateTo.compareTo(a.dateTo));
_healthDataList.addAll(
(healthData.length < 100) ? healthData : healthData.sublist(0, 100));
} catch (error) {
debugPrint("Exception in getHealthDataFromTypes: $error");
}
_healthDataList = health.removeDuplicates(_healthDataList);
for (var data in _healthDataList) {
debugPrint(toJsonString(data));
}
setState(() {
_state = _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
});
}
/// 添加随机健康数据
Future<void> addData() async {
final now = DateTime.now();
final earlier = now.subtract(const Duration(minutes: 20));
bool success = true;
success &= await health.writeHealthData(
value: 1.925,
type: HealthDataType.HEIGHT,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 90,
type: HealthDataType.WEIGHT,
startTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 90,
type: HealthDataType.HEART_RATE,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 90,
type: HealthDataType.STEPS,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 200,
type: HealthDataType.ACTIVE_ENERGY_BURNED,
startTime: earlier,
endTime: now,
);
success &= await health.writeHealthData(
value: 70,
type: HealthDataType.HEART_RATE,
startTime: earlier,
endTime: now);
if (Platform.isIOS) {
success &= await health.writeHealthData(
value: 30,
type: HealthDataType.HEART_RATE_VARIABILITY_SDNN,
startTime: earlier,
endTime: now);
} else {
success &= await health.writeHealthData(
value: 30,
type: HealthDataType.HEART_RATE_VARIABILITY_RMSSD,
startTime: earlier,
endTime: now);
}
success &= await health.writeHealthData(
value: 37,
type: HealthDataType.BODY_TEMPERATURE,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 105,
type: HealthDataType.BLOOD_GLUCOSE,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 1.8,
type: HealthDataType.WATER,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 0.0,
type: HealthDataType.SLEEP_REM,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 0.0,
type: HealthDataType.SLEEP_ASLEEP,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 0.0,
type: HealthDataType.SLEEP_AWAKE,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 0.0,
type: HealthDataType.SLEEP_DEEP,
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 22,
type: HealthDataType.LEAN_BODY_MASS,
startTime: earlier,
endTime: now,
);
success &= await health.writeBloodOxygen(
saturation: 98,
startTime: earlier,
endTime: now,
);
success &= await health.writeWorkoutData(
activityType: HealthWorkoutActivityType.AMERICAN_FOOTBALL,
title: "Random workout name that shows up in Health Connect",
start: now.subtract(const Duration(minutes: 15)),
end: now,
totalDistance: 2430,
totalEnergyBurned: 400,
);
success &= await health.writeBloodPressure(
systolic: 90,
diastolic: 80,
startTime: now,
);
success &= await health.writeMeal(
mealType: MealType.SNACK,
startTime: earlier,
endTime: now,
caloriesConsumed: 1000,
carbohydrates: 50,
protein: 25,
fatTotal: 50,
name: "Banana",
caffeine: 0.002,
vitaminA: 0.001,
vitaminC: 0.002,
vitaminD: 0.003,
vitaminE: 0.004,
vitaminK: 0.005,
b1Thiamin: 0.006,
b2Riboflavin: 0.007,
b3Niacin: 0.008,
b5PantothenicAcid: 0.009,
b6Pyridoxine: 0.010,
b7Biotin: 0.011,
b9Folate: 0.012,
b12Cobalamin: 0.013,
calcium: 0.015,
copper: 0.016,
iodine: 0.017,
iron: 0.018,
magnesium: 0.019,
manganese: 0.020,
phosphorus: 0.021,
potassium: 0.022,
selenium: 0.023,
sodium: 0.024,
zinc: 0.025,
water: 0.026,
molybdenum: 0.027,
chloride: 0.028,
chromium: 0.029,
cholesterol: 0.030,
fiber: 0.031,
fatMonounsaturated: 0.032,
fatPolyunsaturated: 0.033,
fatUnsaturated: 0.065,
fatTransMonoenoic: 0.65,
fatSaturated: 066,
sugar: 0.067,
recordingMethod: RecordingMethod.manual);
success &= await health.writeMenstruationFlow(
flow: MenstrualFlow.medium,
isStartOfCycle: true,
startTime: earlier,
endTime: now,
);
if (Platform.isIOS) {
success &= await health.writeHealthData(
value: 22,
type: HealthDataType.WATER_TEMPERATURE,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 55,
type: HealthDataType.UNDERWATER_DEPTH,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
}
setState(() {
_state = success ? AppState.DATA_ADDED : AppState.DATA_NOT_ADDED;
});
}
/// 删除随机健康数据
Future<void> deleteData() async {
final now = DateTime.now();
final earlier = now.subtract(const Duration(hours: 24));
bool success = true;
for (HealthDataType type in types) {
success &= await health.delete(
type: type,
startTime: earlier,
endTime: now,
);
}
setState(() {
_state = success ? AppState.DATA_DELETED : AppState.DATA_NOT_DELETED;
});
}
/// 获取步数数据
Future<void> fetchStepData() async {
int? steps;
final now = DateTime.now();
final midnight = DateTime(now.year, now.month, now.day);
bool stepsPermission =
await health.hasPermissions([HealthDataType.STEPS]) ?? false;
if (!stepsPermission) {
stepsPermission =
await health.requestAuthorization([HealthDataType.STEPS]);
}
if (stepsPermission) {
try {
steps = await health.getTotalStepsInInterval(midnight, now,
includeManualEntry:
!recordingMethodsToFilter.contains(RecordingMethod.manual));
} catch (error) {
debugPrint("Exception in getTotalStepsInInterval: $error");
}
setState(() {
_nofSteps = (steps == null) ? 0 : steps;
_state = (steps == null) ? AppState.NO_DATA : AppState.STEPS_READY;
});
} else {
setState(() => _state = AppState.DATA_NOT_FETCHED);
}
}
/// 撤销健康数据访问权限
Future<void> revokeAccess() async {
setState(() => _state = AppState.PERMISSIONS_REVOKING);
bool success = false;
try {
await health.revokePermissions();
success = true;
} catch (error) {
debugPrint("Exception in revokeAccess: $error");
}
setState(() {
_state = success
? AppState.PERMISSIONS_REVOKED
: AppState.PERMISSIONS_NOT_REVOKED;
});
}
// UI构建部分省略...
}
此示例展示了如何配置、授权、读取、写入、删除健康数据以及撤销权限。您可以根据需要调整代码以适应具体的应用场景。
更多关于Flutter健康数据访问插件health的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter健康数据访问插件health的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用health
插件来访问健康数据的示例代码。这个插件允许你读取和写入来自iOS和Android设备的健康数据。
首先,确保你已经在pubspec.yaml
文件中添加了health
插件的依赖:
dependencies:
flutter:
sdk: flutter
health: ^0.18.0 # 请检查最新版本号
然后,运行flutter pub get
来安装依赖。
1. 配置权限
在iOS上,你需要在Info.plist
中添加必要的权限请求。例如,如果你需要访问步数数据,你需要添加:
<key>NSHealthShareUsageDescription</key>
<string>我们需要访问您的健康数据来记录您的步数</string>
<key>NSHealthUpdateUsageDescription</key>
<string>我们需要访问您的健康数据来记录您的步数</string>
在Android上,你需要在AndroidManifest.xml
中添加权限请求,但health
插件已经为你处理了大部分权限请求。不过,你可能需要在build.gradle
文件中启用一些特定的功能,比如:
android {
...
defaultConfig {
...
// 启用对Health API的访问
applicationId "com.example.yourapp"
...
}
}
2. 请求权限并读取健康数据
以下是一个简单的Flutter应用示例,它请求访问步数数据的权限并读取步数数据:
import 'package:flutter/material.dart';
import 'package:health/health.dart';
import 'package:health/health_types.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HealthDataScreen(),
);
}
}
class HealthDataScreen extends StatefulWidget {
@override
_HealthDataScreenState createState() => _HealthDataScreenState();
}
class _HealthDataScreenState extends State<HealthDataScreen> {
Health? health;
List<HealthDataPoint<HealthQuantity>>? stepsData;
bool isLoading = true;
bool hasPermission = false;
@override
void initState() {
super.initState();
_requestHealthData();
}
Future<void> _requestHealthData() async {
health = await Health.init();
// 请求权限
bool hasStepsReadPermission = await health?.requestAuthorization(
[
HealthDataType.steps,
],
);
if (hasStepsReadPermission == true) {
setState(() {
hasPermission = true;
});
// 读取步数数据
stepsData = await health?.query({
HealthDataType.steps: QuerySpecification(
startDate: DateTime.now().subtract(Duration(days: 7)),
endDate: DateTime.now(),
),
});
setState(() {
isLoading = false;
});
} else {
setState(() {
isLoading = false;
hasPermission = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('健康数据访问'),
),
body: Center(
child: isLoading
? CircularProgressIndicator()
: hasPermission
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('步数数据:'),
...stepsData!.map((dataPoint) {
return Text(
'${dataPoint.quantity.toDouble().toInt()} 步 on ${dataPoint.startDate.toLocal()}',
);
}),
],
)
: Text('没有权限访问健康数据'),
),
);
}
}
3. 运行应用
确保你已经连接了设备或启动了模拟器,然后运行flutter run
来启动应用。
这个示例展示了如何请求访问步数数据的权限,并读取过去7天的步数数据。你可以根据需要修改数据类型和查询规格来访问其他类型的健康数据。
请注意,health
插件的API可能会随着版本的更新而变化,因此请查阅最新的官方文档以获取最新的使用方法和最佳实践。