Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package visibility in Android 11 for Expo projects #323

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d8892d5
Update AndroidManifest.xml
ytakzk Oct 10, 2022
deb128d
Added Resting Heart Rate Support
Oct 15, 2022
43f8d90
Merge pull request #325 from AaronDsilva97/master
aboveyunhai Nov 5, 2022
6ec2fbd
0.19.1
aboveyunhai Nov 16, 2022
1dcd47c
Merge pull request #327 from StasDoskalenko/bump-version
aboveyunhai Nov 16, 2022
bab5646
flag manually added data on CalorieHistory & HealthHistory
sash20m Jan 11, 2023
c79105b
add types
sash20m Jan 11, 2023
36c1aa4
typo fix
sash20m Jan 11, 2023
f6cce3f
Merge pull request #335 from sash20m/flag-manually-added-data
aboveyunhai Feb 15, 2023
6ed189d
Update INSTALLATION.md (#343)
bsquang Jun 30, 2023
db8fca9
issue 225 (#311)
Nolascoin Jun 30, 2023
39cec12
Bump @sideway/formula from 3.0.0 to 3.0.1 (#339)
dependabot[bot] Aug 29, 2023
dadd8b0
Bump json5 from 2.2.1 to 2.2.3 (#334)
dependabot[bot] Aug 29, 2023
67732d7
Bump async from 2.6.3 to 2.6.4 (#299)
dependabot[bot] Aug 29, 2023
d11d059
Bump decode-uri-component from 0.2.0 to 0.2.2 (#330)
dependabot[bot] Aug 29, 2023
faad186
Use AndroidX (#347)
IgorVanian Nov 14, 2023
1d50c75
Bump react-devtools-core from 4.25.0 to 4.28.4 (#350)
dependabot[bot] Nov 14, 2023
af598e3
Bump @babel/traverse from 7.18.9 to 7.23.2 (#349)
dependabot[bot] Nov 14, 2023
056530f
Added the inLocalTimeZone parameter to the prepareResponse function. …
gearnshaw Nov 14, 2023
5d692e1
Version 0.20.0 (#351)
StasDoskalenko Nov 14, 2023
4dbaade
fix github actions
StasDoskalenkoHover Nov 14, 2023
a367461
Improvement: Add Distance to saveWorkout, to match workout fetching. …
SeanDunford May 17, 2024
eb40bd6
0.21.0
StasDoskalenko May 17, 2024
901115e
Update AndroidManifest.xml
ytakzk Oct 10, 2022
8935968
Merge remote-tracking branch 'origin/master'
Jul 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
on:
push:
branches: master

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: "18"
- uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ async function fetchData() {
const heartrate = await GoogleFit.getHeartRateSamples(opt);
console.log(heartrate);

const restingheartrate = await GoogleFit.getRestingHeartRateSamples(opt);
console.log(restingheartrate);

const bloodpressure = await GoogleFit.getBloodPressureSamples(opt);
console.log(bloodpressure);
}
Expand Down Expand Up @@ -318,6 +321,30 @@ async function fetchData() {
}
]
```
Getting aggregated heart rate samples:

```typescript
/**
* Query for getting aggregated heart rate samples.
* @param options
* @param inLocalTimeZone
*/
getAggregatedHeartRateSamples: (
options: StartAndEndDate & Partial<BucketOptions>,
inLocalTimeZone: boolean
) => Promise<AggregatedHeartRateResponse[]>;
```
Response:
```typescript
[{
startDate: string,
endDate: string,
min: number,
average: number,
max: number,
day: Day,
}]
```

#### 8. Get all activities
<br/>Require scopes: `Scopes.FITNESS_ACTIVITY_READ` & `Scopes.FITNESS_LOCATION_READ`
Expand Down
38 changes: 34 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.android.tools.build:gradle:8.0.2'
}
}

Expand All @@ -17,8 +17,30 @@ def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()

// Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) {
return true
}

return major >= 8
}

android {
compileSdkVersion safeExtGet('compileSdkVersion', 27)
if (supportsNamespace()) {
namespace "com.reactnative.googlefit"
} else {
sourceSets {
main {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
compileSdkVersion safeExtGet('compileSdkVersion', 28)
// buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')

compileOptions {
Expand All @@ -28,7 +50,7 @@ android {

defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
}
Expand All @@ -48,11 +70,19 @@ repositories {
}

dependencies {
def supportLibVersion = safeExtGet('supportLibVersion', null)
def androidXVersion = safeExtGet('androidXVersion', null)

implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation('com.facebook.react:react-native:+') {
exclude group: 'com.android.support'
}
implementation "com.android.support:appcompat-v7:${safeExtGet('supportLibVersion', '27.0.0')}"
if (supportLibVersion && androidXVersion == null) {
implementation "com.android.support:appcompat-v7:$supportLibVersion"
} else {
def defaultAndroidXVersion = "1.+"
implementation "androidx.appcompat:appcompat:${safeExtGet(androidXVersion, defaultAndroidXVersion)}"
}
implementation "com.google.android.gms:play-services-auth:${safeExtGet('authVersion', '+')}"
implementation "com.google.android.gms:play-services-fitness:${safeExtGet('fitnessVersion', '+')}"
}
15 changes: 15 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Wed Oct 11 13:41:26 CEST 2023
android.enableJetifier=true
android.useAndroidX=true
5 changes: 5 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnative.googlefit">

<queries>
<package android:name="com.google.android.apps.fitness" />
</queries>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,23 @@ public void saveWorkout(long startTime, long endTime, ReadableMap options, final
fitnessOptionsBuilder.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_WRITE);
}

if (options.hasKey(DISTANCE_FIELD_NAME)) {
float distance = (float) options.getDouble(DISTANCE_FIELD_NAME);
DataSource distanceDataSource = createWorkoutDataSource(DataType.TYPE_DISTANCE_DELTA);

DataPoint distanceDataPoint = DataPoint.builder(distanceDataSource)
.setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
.setField(Field.FIELD_DISTANCE, distance)
.build();

DataSet distanceDataSet = DataSet.builder(distanceDataSource)
.add(distanceDataPoint)
.build();

sessionInsertBuilder.addDataSet(distanceDataSet);
fitnessOptionsBuilder.addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_WRITE);
}

// add dataSet into session
SessionInsertRequest insertRequest = sessionInsertBuilder.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ private void processDataSet(DataSet dataSet, WritableArray map, boolean basalCal
}
}
stepMap.putDouble("calorie", dp.getValue(field).asFloat() - basal);

/** Checks if data point was added manually by user */
DataSource ds = dp.getOriginalDataSource();
String streamId = ds.getStreamIdentifier();
if (streamId.toLowerCase().indexOf("user_input") != -1) {
stepMap.putBoolean("wasManuallyEntered", true);
} else {
stepMap.putBoolean("wasManuallyEntered", false);
}

map.pushMap(stepMap);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
try {
mAuthInProgress = true;
connectionResult.startResolutionForResult(mActivity, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
} catch (IntentSender.SendIntentException | NullPointerException e) {
Log.i(TAG, "Authorization - Failed again: " + e);
mApiClient.connect();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,38 @@ public void getHeartRateSamples(double startDate,
}
}

@ReactMethod
public void getAggregatedHeartRateSamples(double startDate,
double endDate,
int bucketInterval,
String bucketUnit,
Promise promise) {

try {
HealthHistory healthHistory = mGoogleFitManager.getHealthHistory();
healthHistory.setDataType(DataType.TYPE_HEART_RATE_BPM);
promise.resolve(healthHistory.getAggregatedHeartRateHistory((long)startDate, (long)endDate, bucketInterval, bucketUnit));
} catch (IllegalViewOperationException e) {
promise.reject(e);
}
}

@ReactMethod
public void getRestingHeartRateSamples(double startDate,
double endDate,
int bucketInterval,
String bucketUnit,
Promise promise) {

try {
HealthHistory healthHistory = mGoogleFitManager.getHealthHistory();
healthHistory.setDataType(DataType.TYPE_HEART_RATE_BPM);
promise.resolve(healthHistory.getRestingHeartRateHistory((long)startDate, (long)endDate, bucketInterval, bucketUnit));
} catch (IllegalViewOperationException e) {
promise.reject(e);
}
}

@ReactMethod
public void getHydrationSamples(double startDate,
double endDate,
Expand Down
90 changes: 90 additions & 0 deletions android/src/main/java/com/reactnative/googlefit/HealthHistory.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,84 @@ else if (dataReadResult.getDataSets().size() > 0) {
return map;
}

/**
* GLE added to allow us to aggregate heart rate data.
* It does the same as health history, but adds the aggregation.
* Note there are also some changes to the processDataSet method to allow for the aggregation.
*/
public ReadableArray getAggregatedHeartRateHistory(long startTime, long endTime, int bucketInterval, String bucketUnit) {
DataReadRequest.Builder readRequestBuilder = new DataReadRequest.Builder()
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS);

if (this.dataType == DataType.TYPE_HEART_RATE_BPM) {
readRequestBuilder
.aggregate(this.dataType, DataType.AGGREGATE_HEART_RATE_SUMMARY)
.bucketByTime(bucketInterval, HelperUtil.processBucketUnit(bucketUnit));
} else {
readRequestBuilder.read(this.dataType);
}

DataReadRequest readRequest = readRequestBuilder.build();

DataReadResult dataReadResult = Fitness.HistoryApi.readData(googleFitManager.getGoogleApiClient(), readRequest).await(1, TimeUnit.MINUTES);

WritableArray map = Arguments.createArray();

//Used for aggregated data
if (dataReadResult.getBuckets().size() > 0) {
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
processDataSet(dataSet, map);
}
}
}
//Used for non-aggregated data
else if (dataReadResult.getDataSets().size() > 0) {
for (DataSet dataSet : dataReadResult.getDataSets()) {
processDataSet(dataSet, map);
}
}
return map;
}

public ReadableArray getRestingHeartRateHistory(long startTime, long endTime, int bucketInterval, String bucketUnit) {
DataReadRequest.Builder readRequestBuilder = new DataReadRequest.Builder()
.aggregate(new DataSource.Builder()
.setType(DataSource.TYPE_DERIVED)
.setDataType(DataType.TYPE_HEART_RATE_BPM)
.setAppPackageName("com.google.android.gms")
.setStreamName("resting_heart_rate<-merge_heart_rate_bpm")
.build())
.read(this.dataType)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS);


DataReadRequest readRequest = readRequestBuilder.build();

DataReadResult dataReadResult = Fitness.HistoryApi.readData(googleFitManager.getGoogleApiClient(), readRequest).await(1, TimeUnit.MINUTES);

WritableArray map = Arguments.createArray();

//Used for aggregated data
if (dataReadResult.getBuckets().size() > 0) {
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
processDataSet(dataSet, map);
}
}
}
//Used for non-aggregated data
else if (dataReadResult.getDataSets().size() > 0) {
for (DataSet dataSet : dataReadResult.getDataSets()) {
processDataSet(dataSet, map);
}
}
return map;
}

public boolean saveBloodGlucose(ReadableMap sample) {
this.Dataset = createDataForRequest(
this.dataType,
Expand Down Expand Up @@ -246,10 +324,22 @@ private void processDataSet(DataSet dataSet, WritableArray map) {
if (this.dataType == HealthDataTypes.TYPE_BLOOD_PRESSURE) {
stepMap.putDouble("diastolic", dp.getValue(HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC).asFloat());
stepMap.putDouble("systolic", dp.getValue(HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC).asFloat());
} else if (this.dataType == DataType.TYPE_HEART_RATE_BPM && field.toString().startsWith("average")) {
stepMap.putDouble("average", dp.getValue(Field.FIELD_AVERAGE).asFloat());
stepMap.putDouble("min", dp.getValue(Field.FIELD_MIN).asFloat());
stepMap.putDouble("max", dp.getValue(Field.FIELD_MAX).asFloat());
} else {
stepMap.putDouble("value", dp.getValue(field).asFloat());
}

/** Checks if data point was added manually by user */
DataSource ds = dp.getOriginalDataSource();
String streamId = ds.getStreamIdentifier();
if (streamId.toLowerCase().indexOf("user_input") != -1) {
stepMap.putBoolean("wasManuallyEntered", true);
} else {
stepMap.putBoolean("wasManuallyEntered", false);
}

map.pushMap(stepMap);
}
Expand Down
4 changes: 3 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
### Changelog:

```
0.19.0 ~ Relax dependency version (#313, kudos to @matiaskorhonen)
0.19.1 + Add Resting Heart Rate (#325, kudos to @AaronDsilva97)

0.19.0 ~ Relax dependency version (#313, kudos to @matiaskorhonen)

0.18.3 + Add device Info to step history data (#279, kudos to @kristerkari)
~ Update typescript definitions
Expand Down
4 changes: 4 additions & 0 deletions docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ https://developers.google.com/fit/android/get-api-key
https://developers.google.com/fit/android/get-api-key
```

After that, when you have the client_secrets.json file from OAuth 2.0 Client IDs, you must rename and copy this file to path:
**app/android/app/src/main/resources/client_secrets.json**


### Mostly Automatic installation

`$ react-native link react-native-google-fit`
Expand Down
Loading