react-native-health
react-native-health copied to clipboard
Is Background Processing for Expo Possible?
I am interested in implementing a background listener for steps and active calories burnt as it provides a better user experience in my Expo application (preferably without ejection). Is it possible?
Attempting to do the same with the config plugin below. Will report back whether I'm successful or not.
const {
withAppDelegate,
withEntitlementsPlist
} = require('@expo/config-plugins')
const {
mergeContents
} = require('@expo/config-plugins/build/utils/generateCode')
const RN_HEALTH_IMPORT = '#import "RCTAppleHealthKit.h"'
const RN_HEALTH_INITIALIZE =
'[[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];'
function addImport(src) {
const newSrc = [RN_HEALTH_IMPORT]
return mergeContents({
tag: 'healthkit-import',
src,
newSrc: newSrc.join('\n'),
anchor: /#import "AppDelegate\.h"/,
offset: 1,
comment: '//'
})
}
function addInit(src) {
const newSrc = [RN_HEALTH_INITIALIZE]
return mergeContents({
tag: 'healthkit-init',
src,
newSrc: newSrc.join('\n'),
anchor: /UIViewController/,
offset: -1,
comment: '//'
})
}
const withHealthKitObservers = (config, options = {}) => {
config = withAppDelegate(config, (config) => {
if (config.modResults.language === 'objc') {
config.modResults.contents = addImport(
config.modResults.contents
).contents
config.modResults.contents = addInit(config.modResults.contents).contents
} else {
WarningAggregator.addWarningIOS(
'withHealthKitObservers',
'Swift AppDelegate files are not supported yet.'
)
}
return config
})
config = withEntitlementsPlist(config, (config) => {
config.modResults[
'com.apple.developer.healthkit.background-delivery'
] = true
return config
})
return config
}
module.exports = withHealthKitObservers
Any luck @jhbarnett jhbarnett??
Yeah @brianfoody it's working 90% great. We're still trying to pinpoint an issue with background delivery when the app is in a killed state, but near as we can tell this plugin is making all the right mods.
For anyone else that stumbles upon this, here are the steps to integrate in your own project. I'll explore a PR here once our app has shipped.
@jhbarnett How did everything work out in the end?
@brianfoody @AhmadHoranieh Y'all had any success with this?
Attempting to do the same with the config plugin below. Will report back whether I'm successful or not.
const { withAppDelegate, withEntitlementsPlist } = require('@expo/config-plugins') const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode') const RN_HEALTH_IMPORT = '#import "RCTAppleHealthKit.h"' const RN_HEALTH_INITIALIZE = '[[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];' function addImport(src) { const newSrc = [RN_HEALTH_IMPORT] return mergeContents({ tag: 'healthkit-import', src, newSrc: newSrc.join('\n'), anchor: /#import "AppDelegate\.h"/, offset: 1, comment: '//' }) } function addInit(src) { const newSrc = [RN_HEALTH_INITIALIZE] return mergeContents({ tag: 'healthkit-init', src, newSrc: newSrc.join('\n'), anchor: /UIViewController/, offset: -1, comment: '//' }) } const withHealthKitObservers = (config, options = {}) => { config = withAppDelegate(config, (config) => { if (config.modResults.language === 'objc') { config.modResults.contents = addImport( config.modResults.contents ).contents config.modResults.contents = addInit(config.modResults.contents).contents } else { WarningAggregator.addWarningIOS( 'withHealthKitObservers', 'Swift AppDelegate files are not supported yet.' ) } return config }) config = withEntitlementsPlist(config, (config) => { config.modResults[ 'com.apple.developer.healthkit.background-delivery' ] = true return config }) return config } module.exports = withHealthKitObservers
For anyone who has build issue with eas build with the above config. WarningAggregator
is not imported.
Just do
const {
withAppDelegate,
withEntitlementsPlist,
WarningAggregator,
} = require("@expo/config-plugins")
You will be fine.
Update: I cannot make the above code work, even I updated the AppDelegate.mm
to be the same as it should be.
Has anyone had success with this?
I was able to query the health records in a location-based task, but the problem is that the health records are encrypted when the device is locked. An ideal solution would allow for a bridge to be set up within Expo that allows us to subscribe to new workouts, but I don't think there is a way to do this, currently.
@jclif @jhbarnett Any success with this? It would be great to be able to fetch the HK data in the background :)
const {
withAppDelegate,
withEntitlementsPlist,
withInfoPlist,
} = require('@expo/config-plugins')
const {
mergeContents
} = require('@expo/config-plugins/build/utils/generateCode')
const RN_HEALTH_IMPORT = '#import "RCTAppleHealthKit.h"'
const RN_HEALTH_BRIDGE = ' RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];'
const RN_HEALTH_INITIALIZE = ' [[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];'
const HEALTH_SHARE = 'Allow $(PRODUCT_NAME) to check health info'
const HEALTH_UPDATE = 'Allow $(PRODUCT_NAME) to update health info'
const HEALTH_CLINIC_SHARE = 'Allow $(PRODUCT_NAME) to check health clinical info'
function addImport(src) {
const newSrc = [RN_HEALTH_IMPORT]
return mergeContents({
tag: 'healthkit-import',
src,
newSrc: newSrc.join('\n'),
anchor: /#import "AppDelegate\.h"/,
offset: 1,
comment: '//'
})
}
function addInit(src) {
const newSrc = [RN_HEALTH_BRIDGE, RN_HEALTH_INITIALIZE]
return mergeContents({
tag: 'healthkit-init',
src,
newSrc: newSrc.join('\n'),
anchor: /self.initialProps = @{};/,
offset: 1,
comment: ' //'
})
}
const withHealthKit = (
config,
{ healthSharePermission, healthUpdatePermission, isClinicalDataEnabled, healthClinicalDescription } = {},
) => {
// Add import
config = withAppDelegate(config, (config) => {
if (config.modResults.language === 'objcpp') {
config.modResults.contents = addImport(config.modResults.contents).contents
config.modResults.contents = addInit(config.modResults.contents).contents
}
return config
})
// Add permissions
config = withInfoPlist(config, (config) => {
config.modResults.NSHealthShareUsageDescription =
healthSharePermission ||
config.modResults.NSHealthShareUsageDescription ||
HEALTH_SHARE
config.modResults.NSHealthUpdateUsageDescription =
healthUpdatePermission ||
config.modResults.NSHealthUpdateUsageDescription ||
HEALTH_UPDATE
isClinicalDataEnabled ?
config.modResults.NSHealthClinicalHealthRecordsShareUsageDescription =
healthClinicalDescription ||
config.modResults.NSHealthClinicalHealthRecordsShareUsageDescription ||
HEALTH_CLINIC_SHARE :
null
return config
})
// Add entitlements. These are automatically synced when using EAS build for production apps.
config = withEntitlementsPlist(config, (config) => {
config.modResults['com.apple.developer.healthkit'] = true
config.modResults['com.apple.developer.healthkit.background-delivery'] = true
if (
!Array.isArray(config.modResults['com.apple.developer.healthkit.access'])
) {
config.modResults['com.apple.developer.healthkit.access'] = []
}
if (isClinicalDataEnabled) {
config.modResults['com.apple.developer.healthkit.access'].push(
'health-records',
)
// Remove duplicates
config.modResults['com.apple.developer.healthkit.access'] = [
...new Set(config.modResults['com.apple.developer.healthkit.access']),
]
}
return config
})
return config
}
module.exports = withHealthKit
which modifies the AppDelegate.mm to the following:
#import "AppDelegate.h"
// @generated begin healthkit-import - expo prebuild (DO NOT MODIFY) sync-283e92dde8b719d45e241f992dc4e0cceb751e1e
#import "RCTAppleHealthKit.h"
// @generated end healthkit-import
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"main";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
// @generated begin healthkit-init - expo prebuild (DO NOT MODIFY) sync-f4def85acdf20e650ea4209ec70a05de7e0ebb70
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
[[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];
// @generated end healthkit-init
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
I'm still testing if this works, but I think I'm in the right path! PS: I'm using the latest version of this lib and Expo 49
@samuthekid any success with fetching HK data in background?
@rickyhonline I didn't leave any feedback, but yeah! It works very well!
Right now, I'm using patch-package for this, so here's my patch:
diff --git a/node_modules/react-native-health/app.plugin.js b/node_modules/react-native-health/app.plugin.js
index 8dbb66a..b003319 100644
--- a/node_modules/react-native-health/app.plugin.js
+++ b/node_modules/react-native-health/app.plugin.js
@@ -1,13 +1,57 @@
-const { withEntitlementsPlist, withInfoPlist } = require('@expo/config-plugins')
+const {
+ withAppDelegate,
+ withEntitlementsPlist,
+ withInfoPlist,
+} = require('@expo/config-plugins')
+const {
+ mergeContents
+} = require('@expo/config-plugins/build/utils/generateCode')
+
+const RN_HEALTH_IMPORT = '#import "RCTAppleHealthKit.h"'
+const RN_HEALTH_BRIDGE = ' RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];'
+const RN_HEALTH_INITIALIZE = ' [[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];'
const HEALTH_SHARE = 'Allow $(PRODUCT_NAME) to check health info'
const HEALTH_UPDATE = 'Allow $(PRODUCT_NAME) to update health info'
const HEALTH_CLINIC_SHARE = 'Allow $(PRODUCT_NAME) to check health clinical info'
+function addImport(src) {
+ const newSrc = [RN_HEALTH_IMPORT]
+ return mergeContents({
+ tag: 'healthkit-import',
+ src,
+ newSrc: newSrc.join('\n'),
+ anchor: /#import "AppDelegate\.h"/,
+ offset: 1,
+ comment: '//'
+ })
+}
+
+function addInit(src) {
+ const newSrc = [RN_HEALTH_BRIDGE, RN_HEALTH_INITIALIZE]
+ return mergeContents({
+ tag: 'healthkit-init',
+ src,
+ newSrc: newSrc.join('\n'),
+ anchor: /self.initialProps = @{};/,
+ offset: 1,
+ comment: ' //'
+ })
+}
+
const withHealthKit = (
config,
{ healthSharePermission, healthUpdatePermission, isClinicalDataEnabled, healthClinicalDescription } = {},
) => {
+ // Add import
+ config = withAppDelegate(config, (config) => {
+ if (config.modResults.language === 'objcpp') {
+ config.modResults.contents = addImport(config.modResults.contents).contents
+ config.modResults.contents = addInit(config.modResults.contents).contents
+ }
+ return config
+ })
+
// Add permissions
config = withInfoPlist(config, (config) => {
config.modResults.NSHealthShareUsageDescription =
@@ -31,6 +75,7 @@ const withHealthKit = (
// Add entitlements. These are automatically synced when using EAS build for production apps.
config = withEntitlementsPlist(config, (config) => {
config.modResults['com.apple.developer.healthkit'] = true
+ config.modResults['com.apple.developer.healthkit.background-delivery'] = true
if (
!Array.isArray(config.modResults['com.apple.developer.healthkit.access'])
) {
which is located in the root folder like this:
I think the owners could make a little update with this new version of the expo plugin ;)