Skip to content

Commit

Permalink
Added the inLocalTimeZone parameter to the prepareResponse function. (#…
Browse files Browse the repository at this point in the history
…338)

* Added the inLocalTimeZone parameter to the prepareResponse function. This defaults to false (existing behaviour) so is backwards compatible.
When inLocalTimeZone = false, dates are returned in the format: "endDate":"2022-12-22T16:51:30.000Z"
When inLocalTimeZone = true, dates are returned in the format: "endDate":"2022-12-22T11:51:30.000-0500"

This allows performance improvements, because you don't have to perform further date processing on the values to convert them back into the local time zone.

This PR adds the flag as a parameter to getSleepSamples. It can easily be added to other functions as required.

* Added logging to test running in app code

* Added the code to retrieve aggregated heart rates.

* Fixed call to get health history.

* Added extra logging

* Fixed reading values and removed logging

* Removed more logging.

* Fixed issue where all values were overwriting the average.

* Fixed a typo.

* Added the 'in local timezone' option to aggregated heart rates
  • Loading branch information
gearnshaw authored Nov 14, 2023
1 parent af598e3 commit 056530f
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,22 @@ 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,
Expand Down
45 changes: 45 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,47 @@ 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()
Expand Down Expand Up @@ -283,6 +324,10 @@ 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());
}
Expand Down
18 changes: 17 additions & 1 deletion index.android.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ declare module 'react-native-google-fit' {
options: StartAndEndDate & Partial<BucketOptions>
) => Promise<HeartRateResponse[]>;

getAggregatedHeartRateSamples: (
options: StartAndEndDate & Partial<BucketOptions>,
inLocalTimeZone: boolean
) => Promise<AggregatedHeartRateResponse[]>;

/**
* Query for getting resting heart rate samples. the options object is used to setup a query to retrieve relevant samples.
* @param {Object} options getRestingHeartRateSamples accepts an options object startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
Expand Down Expand Up @@ -181,9 +186,11 @@ declare module 'react-native-google-fit' {
/**
* Get the sleep sessions over a specified date range.
* @param {Object} options getSleepData accepts an options object containing required startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
* @param inLocalTimeZone return start and end dates in local time zone rather than converting to UTC.
*/
getSleepSamples: (
options: Partial<StartAndEndDate>
options: Partial<StartAndEndDate>,
inLocalTimeZone: boolean
) => Promise<SleepSampleResponse[]>

saveSleep: (
Expand Down Expand Up @@ -340,6 +347,15 @@ declare module 'react-native-google-fit' {
wasManuallyEntered: boolean
};

export type AggregatedHeartRateResponse = {
startDate: string,
endDate: string,
min: number,
average: number,
max: number,
day: Day,
}

export type BloodPressureResponse = {
startDate: string,
endDate: string,
Expand Down
19 changes: 17 additions & 2 deletions index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,20 @@ class RNGoogleFit {
return result;
}

getAggregatedHeartRateSamples = async (options, inLocalTimeZone = false) => {
const { startDate, endDate, bucketInterval, bucketUnit } = prepareInput(options);
const result = await googleFit.getAggregatedHeartRateSamples(
startDate,
endDate,
bucketInterval,
bucketUnit
);
if (result.length > 0) {
return prepareResponse(result, 'average', inLocalTimeZone);
}
return result;
}

getRestingHeartRateSamples = async (options) => {
const { startDate, endDate, bucketInterval, bucketUnit } = prepareInput(options);
const result = await googleFit.getRestingHeartRateSamples(
Expand Down Expand Up @@ -689,17 +703,18 @@ class RNGoogleFit {
/**
* Get the sleep sessions over a specified date range.
* @param {Object} options getSleepData accepts an options object containing required startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
* @param inLocalTimeZone return start and end dates in local time zone rather than converting to UTC
*/

getSleepSamples = async (options) => {
getSleepSamples = async (options, inLocalTimeZone = false) => {
const { startDate, endDate } = prepareInput(options);

const result = await googleFit.getSleepSamples(
startDate,
endDate
);

return prepareResponse(result, "addedBy");
return prepareResponse(result, "addedBy", inLocalTimeZone);
}

saveSleep = async (options) => {
Expand Down
11 changes: 8 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function prepareInput(options) {
return { startDate, endDate, bucketInterval, bucketUnit };
}

export function prepareResponse(response, byKey = 'value') {
export function prepareResponse(response, byKey = 'value', inLocalTimeZone = false) {
return response
.map(el => {
if (!isNil(el[byKey])) {
Expand All @@ -57,8 +57,13 @@ export function prepareResponse(response, byKey = 'value') {
// the Chrome V8 debugger, but not on device. Using momentJS here to get around this issue.
// el.startDate = new Date(el.startDate).toISOString()
// el.endDate = new Date(el.endDate).toISOString()
el.startDate = moment(el.startDate).toISOString()
el.endDate = moment(el.endDate).toISOString()
if (inLocalTimeZone) {
el.startDate = moment.parseZone(el.startDate).toISOString(true)
el.endDate = moment.parseZone(el.endDate).toISOString(true)
} else {
el.startDate = moment(el.startDate).toISOString()
el.endDate = moment(el.endDate).toISOString()
}
return el
}
})
Expand Down

0 comments on commit 056530f

Please sign in to comment.