Skip to content

Commit 4ddb0da

Browse files
authored
feat: Add support for background access permission (READ_HEALTH_DATA_IN_BACKGROUND) (matinzd#212)
* docs: add documentation for BackgroundAccessPermission * feat: add BackgroundAccessPermission implementation * chore: update dependencies for BackgroundAccessPermission support
1 parent 7785bb5 commit 4ddb0da

File tree

13 files changed

+15373
-21041
lines changed

13 files changed

+15373
-21041
lines changed

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ buildscript {
88
}
99

1010
dependencies {
11-
classpath "com.android.tools.build:gradle:7.2.1"
11+
classpath "com.android.tools.build:gradle:8.1.2"
1212
// noinspection DifferentKotlinGradleVersion
1313
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1414
}

android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
HealthConnect_kotlinVersion=1.8.0
22
HealthConnect_minSdkVersion=26
33
HealthConnect_targetSdkVersion=34
4-
HealthConnect_compileSdkVersion=34
4+
HealthConnect_compileSdkVersion=35
55
HealthConnect_ndkversion=21.4.7075529

android/src/main/java/dev/matinzd/healthconnect/permissions/PermissionUtils.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ class PermissionUtils {
2323
return@mapNotNull HealthPermission.PERMISSION_READ_HEALTH_DATA_HISTORY
2424
}
2525

26-
val recordClass = reactRecordTypeToClassMap[recordType]
27-
?: throw InvalidRecordType()
26+
if (accessType == "read" && recordType == "BackgroundAccessPermission") {
27+
return@mapNotNull HealthPermission.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND
28+
}
29+
30+
val recordClass = reactRecordTypeToClassMap[recordType] ?: throw InvalidRecordType()
2831

2932
when (accessType) {
3033
"write" -> HealthPermission.getWritePermission(recordClass)
@@ -40,6 +43,7 @@ class PermissionUtils {
4043

4144
fun mapPermissionResult(grantedPermissions: Set<String>): WritableNativeArray {
4245
return WritableNativeArray().apply {
46+
// Handle regular permissions
4347
for ((recordType, recordClass) in reactRecordTypeToClassMap) {
4448
val readPermissionForRecord = HealthPermission.getReadPermission(recordClass)
4549
val writePermissionForRecord = HealthPermission.getWritePermission(recordClass)
@@ -52,8 +56,17 @@ class PermissionUtils {
5256
pushMap(ReactPermission(AccessType.WRITE, recordType).toReadableMap())
5357
}
5458
}
59+
60+
// Handle special permissions
61+
if (grantedPermissions.contains(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE)) {
62+
pushMap(ReactPermission(AccessType.WRITE, "ExerciseRoute").toReadableMap())
63+
}
64+
65+
if (grantedPermissions.contains(HealthPermission.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND)
66+
) {
67+
pushMap(ReactPermission(AccessType.READ, "BackgroundAccessPermission").toReadableMap())
68+
}
5569
}
5670
}
57-
5871
}
5972
}

docs/docs/api/methods/03-requestPermission.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ const requestPermissions = () => {
3333
};
3434
```
3535

36-
If your app needs to write exercise routes, can include it as a special permission:
36+
## Special Permissions
37+
38+
### Exercise Route Permission
39+
40+
If your app needs to write exercise routes, you can include it as a special permission:
3741

3842
```ts
3943
import { requestPermission } from 'react-native-health-connect';
@@ -56,4 +60,32 @@ const requestPermissions = () => {
5660
console.log('Granted permissions ', { permissions });
5761
});
5862
};
63+
```
64+
65+
### Background Access Permission
66+
67+
If your app needs to read health data in the background, you can request the background access permission:
68+
69+
```ts
70+
import { requestPermission } from 'react-native-health-connect';
71+
72+
const requestBackgroundAccess = () => {
73+
requestPermission([
74+
{
75+
accessType: 'read',
76+
recordType: 'BackgroundAccessPermission',
77+
},
78+
// Other permissions you need...
79+
{
80+
accessType: 'read',
81+
recordType: 'Steps',
82+
},
83+
{
84+
accessType: 'read',
85+
recordType: 'HeartRate',
86+
}
87+
]).then((permissions) => {
88+
console.log('Granted permissions ', { permissions });
89+
});
90+
};
5991
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: Background Access Permission
3+
---
4+
5+
# Background Access Permission
6+
7+
Health Connect provides a special permission that allows your app to read health data in the background. This is useful for apps that need to monitor health data continuously, such as fitness tracking apps.
8+
9+
## Setup
10+
11+
### 1. Add the permission to your AndroidManifest.xml
12+
13+
First, you need to declare the background access permission in your app's `AndroidManifest.xml` file:
14+
15+
```xml
16+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND"/>
17+
```
18+
19+
### 2. Request the permission in your app
20+
21+
Then, include the background access permission in your permission request:
22+
23+
```ts
24+
import { requestPermission } from 'react-native-health-connect';
25+
26+
const requestPermissions = () => {
27+
requestPermission([
28+
{
29+
accessType: 'read',
30+
recordType: 'BackgroundAccessPermission',
31+
},
32+
// Other permissions...
33+
]).then((permissions) => {
34+
console.log('Granted permissions ', { permissions });
35+
});
36+
};
37+
```
38+
39+
## Android Implementation
40+
41+
Under the hood, this permission maps to `HealthPermission.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND` in the Android Health Connect API. This permission allows your app to read health data even when it's not in the foreground.
42+
43+
## Checking for Background Access
44+
45+
You can check if your app has been granted background access permission using the `getGrantedPermissions` method:
46+
47+
```ts
48+
import { getGrantedPermissions } from 'react-native-health-connect';
49+
50+
const checkBackgroundAccess = async () => {
51+
const permissions = await getGrantedPermissions();
52+
const hasBackgroundAccess = permissions.some(
53+
(permission) =>
54+
permission.accessType === 'read' &&
55+
permission.recordType === 'BackgroundAccessPermission'
56+
);
57+
58+
console.log('Has background access:', hasBackgroundAccess);
59+
};
60+
```
61+
62+
## Important Notes
63+
64+
- Background access is a powerful permission that should be used responsibly
65+
- Make sure to explain to users why your app needs background access to their health data
66+
- This permission is only available on Android devices with Health Connect support

docs/docs/api/overview.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@ title: Overview
2020
| aggregateRecord | Reads aggregated results according to requested read criteria, for e.g, data origin filter and within a time range. |
2121
| deleteRecordsByUuids | Deletes one or more records by their identifiers. Deletion of multiple records is executed in a single transaction - if one fails, none is deleted. |
2222
| deleteRecordsByTimeRange | Deletes any record of the given record type in the given time range (automatically filtered to a record belonging to the calling application). Deletion of multiple records is executed in a transaction - if one fails, none is deleted. |
23+
| requestExerciseRoute | Requests permission to access exercise route data for a specific exercise session. |
24+
25+
## Special Permissions
26+
27+
| **Permission Type** | **Description** |
28+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29+
| BackgroundAccessPermission | Allows your app to read health data in the background, even when your app is not in the foreground. |

docs/docs/permissions.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,48 @@ You will need to use [EAS Build](https://docs.expo.dev/eas/) and [Config plugins
159159
| WriteExerciseRoute | android.permission.health.WRITE_EXERCISE_ROUTE | N/A |
160160

161161
You can read more about data types and permissions [here](https://developer.android.com/guide/health-and-fitness/health-connect/data-and-data-types/data-types).
162+
163+
## Special Permissions
164+
165+
In addition to the standard record type permissions, Health Connect provides special permissions for specific functionality:
166+
167+
### Background Access Permission
168+
169+
This permission allows your app to read health data in the background, even when your app is not in the foreground.
170+
171+
First, add the background access permission to your `AndroidManifest.xml`:
172+
173+
```xml
174+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND"/>
175+
```
176+
177+
Then, request the permission in your app:
178+
179+
```ts
180+
// Request background access permission
181+
requestPermission([
182+
{
183+
accessType: 'read',
184+
recordType: 'BackgroundAccessPermission',
185+
},
186+
// Other permissions...
187+
]);
188+
```
189+
190+
Under the hood, this maps to `HealthPermission.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND` in the Android Health Connect API.
191+
192+
See the [Background Access Permission](./api/methods/17-backgroundAccessPermission.md) documentation for more details.
193+
194+
### Exercise Route Permission
195+
196+
This special permission is required to write exercise routes:
197+
198+
```ts
199+
requestPermission([
200+
{
201+
accessType: 'write',
202+
recordType: 'ExerciseRoute',
203+
},
204+
// Other permissions...
205+
]);
206+
```

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTES"/>
99
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE"/>
1010
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
11+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND"/>
1112

1213
<application
1314
android:name=".MainApplication"

example/src/App.tsx

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33
import {
44
Button,
55
NativeSyntheticEvent,
6+
ScrollView,
67
StyleSheet,
78
TextInput,
89
TextInputChangeEventData,
@@ -241,6 +242,10 @@ export default function App() {
241242

242243
const requestSamplePermissions = () => {
243244
requestPermission([
245+
{
246+
accessType: 'read',
247+
recordType: 'BackgroundAccessPermission',
248+
},
244249
{
245250
accessType: 'read',
246251
recordType: 'Steps',
@@ -339,45 +344,47 @@ export default function App() {
339344
};
340345

341346
return (
342-
<View style={styles.container}>
343-
<Button title="Initialize" onPress={initializeHealthConnect} />
344-
<Button
345-
title="Open Health Connect settings"
346-
onPress={openHealthConnectSettings}
347-
/>
348-
<Button
349-
title="Open Health Connect data management"
350-
onPress={() => openHealthConnectDataManagement()}
351-
/>
352-
<Button title="Check availability" onPress={checkAvailability} />
353-
<Button
354-
title="Request sample permissions"
355-
onPress={requestSamplePermissions}
356-
/>
357-
<Button title="Get granted permissions" onPress={grantedPermissions} />
358-
<Button title="Revoke all permissions" onPress={revokeAllPermissions} />
359-
<Button title="Insert sample data" onPress={insertSampleData} />
360-
<Button title="Read sample data" onPress={readSampleData} />
361-
<Button title="Read specific data" onPress={readSampleDataSingle} />
362-
<Button title="Aggregate sample data" onPress={aggregateSampleData} />
363-
<Button
364-
title="Aggregate sample group data by duration"
365-
onPress={aggregateSampleGroupByDuration}
366-
/>
367-
<Button
368-
title="Aggregate sample group data by period"
369-
onPress={aggregateSampleGroupByPeriod}
370-
/>
371-
<Button title="Insert random exercise" onPress={insertRandomExercise} />
372-
<TextInput
373-
id="record-id"
374-
placeholder="Record ID"
375-
value={recordId}
376-
onChange={updateRecordId}
377-
/>
378-
<Button title="Read exercise" onPress={readExercise} />
379-
<Button title="Request exercise route" onPress={readExerciseRoute} />
380-
</View>
347+
<ScrollView>
348+
<View style={styles.container}>
349+
<Button title="Initialize" onPress={initializeHealthConnect} />
350+
<Button
351+
title="Open Health Connect settings"
352+
onPress={openHealthConnectSettings}
353+
/>
354+
<Button
355+
title="Open Health Connect data management"
356+
onPress={() => openHealthConnectDataManagement()}
357+
/>
358+
<Button title="Check availability" onPress={checkAvailability} />
359+
<Button
360+
title="Request sample permissions"
361+
onPress={requestSamplePermissions}
362+
/>
363+
<Button title="Get granted permissions" onPress={grantedPermissions} />
364+
<Button title="Revoke all permissions" onPress={revokeAllPermissions} />
365+
<Button title="Insert sample data" onPress={insertSampleData} />
366+
<Button title="Read sample data" onPress={readSampleData} />
367+
<Button title="Read specific data" onPress={readSampleDataSingle} />
368+
<Button title="Aggregate sample data" onPress={aggregateSampleData} />
369+
<Button
370+
title="Aggregate sample group data by duration"
371+
onPress={aggregateSampleGroupByDuration}
372+
/>
373+
<Button
374+
title="Aggregate sample group data by period"
375+
onPress={aggregateSampleGroupByPeriod}
376+
/>
377+
<Button title="Insert random exercise" onPress={insertRandomExercise} />
378+
<TextInput
379+
id="record-id"
380+
placeholder="Record ID"
381+
value={recordId}
382+
onChange={updateRecordId}
383+
/>
384+
<Button title="Read exercise" onPress={readExercise} />
385+
<Button title="Request exercise route" onPress={readExerciseRoute} />
386+
</View>
387+
</ScrollView>
381388
);
382389
}
383390

@@ -387,6 +394,7 @@ const styles = StyleSheet.create({
387394
alignItems: 'center',
388395
justifyContent: 'center',
389396
rowGap: 16,
397+
padding: 16,
390398
},
391399
box: {
392400
width: 60,

0 commit comments

Comments
 (0)