kingstinct / react-native-healthkit

HealthKit bindings for React Native with TypeScript
https://kingstinct.com/react-native-healthkit/
MIT License
192 stars 42 forks source link

Set up observer queries in didFinishLaunchingWithOptions to align with Apples recommendations #51

Open robertherber opened 1 year ago

robertherber commented 1 year ago

To comply with Apple recommendations and to prevent any race conditions that might arise from initialising it later, as is the case now.

There are two parts to this:

mafen commented 1 year ago

How does one use enableBackgroundDelivery today?, because i am seeing that in apples docs, they have something called completion handler, is that needed to do it ?

robertherber commented 1 year ago

@mafen It's available in this library already (and seems to work allright) - but according to the Apple docs it's recommended to call it earlier in the lifecycle (in didFinishLaunchingWithOptions, i.e. before any JS code is initialized.

mafen commented 1 year ago

Any examples on the part that is in already? Like i want to use enableBackgroundDelivery in it's current from, and i have tried

HealthKit.enableBackgroundDelivery(HKQuantityTypeIdentifier.activeEnergyBurned, HKUpdateFrequency.immediate) But where do I get the result of this, what calls my method?

I would love an example of how to use this function

robertherber commented 1 year ago

@mafen The completion handler is solely indicating success or failure in enabling background delivery - not for getting the data itself (and in react-native-healthkit it's returned as a promise instead of a callback - to improve the developer experience).

I think the methods you use to fetch/subscribe to data are the same as without background delivery.

mafen commented 1 year ago

I have gotten it working with subscribe to data but only when press the simulate background fetch in XCode and it is very flaky on a real ios device, could only get it work well in the simulator but not after terminating the app.

robertherber commented 1 year ago

@mafen In the Apple Docs about Background Delivery it states that most types of data can only be subscribed on to an hourly basis at most, I guess that might be what you’re experiencing?

mafen commented 1 year ago

I am subscribeing to active energy burned and that is supposed to be on an immediate frequently

robertherber commented 1 year ago

@mafen Here it's not listed as one of the immediates.

mafen commented 1 year ago

@mafen Here it's not listed as one of the immediates.

I am maybe wrong, but I thought that was only for watch OS

robertherber commented 1 year ago

@ha3 I started looking at implementing a config plugin for this. However when reading the docs again I realised it's not actually recommending to call the enableBackgroundDelivery in didFinishLaunchingWithOptions, but rather to set up all observer queries there:

As soon as your app launches, HealthKit calls the update handler for any observer queries that match the newly saved data. If you plan on supporting background delivery, set up all your observer queries in your app delegate’s application(_:didFinishLaunchingWithOptions:) method. By setting up the queries in application(_:didFinishLaunchingWithOptions:), you ensure that you’ve instantiated your queries, and they’re ready to use before HealthKit delivers the updates.

I guess this makes more sense, and is also more difficult to achieve. What is your experience with this - do you miss out on background updates? Are you using observer queries or maybe some other method to fetch the data?

ha3 commented 1 year ago

@robertherber I understand that this means changing useSubscribeToChanges implementation in general, and adding an additional native step for the installation. I agree that it can be more difficult to implement; but I'm not sure about the possible consequences of not following official advice on this.

We haven't ship HealthKit observers to production yet; since there is no way to retrieve changed data between observer calls for now (https://github.com/Kingstinct/react-native-healthkit/issues/50). So, we can't be sure if there is any miss or not.

Basically this is how we use the observers:

let isBackgroundObserversSetup = false;

const setupBackgroundObservers = () => {
  if (isBackgroundObserversSetup) {
    return;
  }

  isBackgroundObserversSetup = true;

  ([
    HKQuantityTypeIdentifier.activeEnergyBurned,
    ...
  ] as const).forEach(permission => {
    HealthKit.enableBackgroundDelivery(permission, HKUpdateFrequency.immediate);
  });
};

const HealthKitProvider = ({ children }: { children: React.ReactNode }) => {
  setupBackgroundObservers();

  HealthKit.useSubscribeToChanges(
    HKQuantityTypeIdentifier.activeEnergyBurned,
    async () => {
      const data = await HealthKit.getMostRecentQuantitySample(HKQuantityTypeIdentifier.activeEnergyBurned);

      if (!data) {
        return;
      }

      // do sth with data
    }
  );

  return <>{children}</>;
};

const App = () => {
  return (
    <HealthKitProvider>
      ...
    </HealthKitProvider>
  );
}
matteo-hertel commented 1 year ago

@ha3 thank you for the example you posted, helped a lot to understand how to set it up The one thing that is still not clear to me is the background delivery when the app is in the background or closed, will the useSubscribeToChanges still fire?

For example, I'm trying to subscribe to the latest workout, I would like to send the user a notification once they completed a workout, I've created a provider like your example above but it only fires when the app is in the foreground, if the app is in a closed state I don't receive the data and therefore can't show the notification, any idea how to achieve that?

justingshreve commented 11 months ago

I'm trying to get new workouts, like @matteo-hertel but not having any luck. I have a provider, similar to what was referenced above but new workouts are not being picked up by the subscription. The function in the useSubscribeToChanges is not called. The logs are stating true when enableBackgroundDelivery is called and on first load, I see SUBSCRIBE: change found, but even with the app in the foreground, I log an activity with connected watch and nothing is triggered. Any help or pointers would be greatly appreciated.

export const AppleHealthContext = createContext(null)
let isBackgroundObserversSetup = false

const setupBackgroundObservers = async () => {
  if (isBackgroundObserversSetup) return
  isBackgroundObserversSetup = true
  const status = await HealthKit.enableBackgroundDelivery(HKWorkoutTypeIdentifier, HKUpdateFrequency.immediate)
  console.log('Enable Background Status', status)
}

export const AppleHealthProvider = ({ children }) => {
  setupBackgroundObservers()

  HealthKit.useSubscribeToChanges(
    HKWorkoutTypeIdentifier,
    async () => {
      console.log('SUBSCRIBE: change found')
      await update() // posts new workouts to endpoint
    }
  )

  return (<AppleHealthContext.Provider>
    {children}
  </AppleHealthContext.Provider>)
}
bewallyt commented 11 months ago

@justingshreve I'm running into the same issue - were you able to find a solution?

justingshreve commented 10 months ago

I was able to get this working, but it is still a bit mysterious. I had a few providers and moved the HealthKit code to the top level provider. I also added fetch and processing UIBackgroundModes: ['remote-notification', 'fetch', 'processing'],. Even with that, when I install new development build, testing it is spotty. Works, then does not. I did notice that if the build is installed for about a day, it does seem to become more reliable, so I'm assuming iOS is doing something similar to what is done with background tasks, and it takes some time for background queries to be engaged. It'd be great if this was more straightforward though.

davidyang commented 9 months ago

I'm not sure I understand how enableBackgroundDelivery is actually working. If I subscribeToChanges and in the callback do a console.log, will I see that console.log even if my app isn't open? Or does this just mean that when my app opens, all those ObserverQueries will get run?

The part that is confusing comes from this part of the Apple documentation

As soon as your app launches, HealthKit calls the update handler for any observer queries that match the newly saved data.

Is that referring to the app launching in the background or when it is foregrounded again?