-
-
Notifications
You must be signed in to change notification settings - Fork 51
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
feat: add workout mirroring support #137
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis update introduces workout session mirroring functionality for the React Native Healthkit integration. New native methods and properties are added in iOS code to manage workout sessions and delegate events for remote workout state changes, errors, and data reception. Corresponding TypeScript modules have been updated to export and type these new features. Additionally, mocks and type safety improvements have been implemented to support the new workout session mirroring handler across the codebase. Changes
Sequence Diagram(s)sequenceDiagram
participant JS as JavaScript App
participant Util as workoutSessionMirroringStartHandler (TS Utility)
participant Native as ReactNativeHealthkit (Native Module)
participant Session as HKWorkoutSession
JS->>Util: Invoke workoutSessionMirroringStartHandler()
Util->>Native: Call native workoutSessionMirroringStartHandler(resolve, reject)
Native->>Session: Set _workoutSession and assign delegate
Session-->>Native: Trigger delegate methods (state change, error, data)
Native-->>JS: Emit events (onRemoteWorkoutStateChange, onRemoteWorkoutError, onRemoteWorkoutDataReceived)
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure. 🔧 ESLint
src/native-types.tsOops! Something went wrong! :( ESLint: 8.57.1 Error [ERR_REQUIRE_ESM]: require() of ES Module /node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/eslint-plugin-functional/lib/index.js from /node_modules/.pnpm/@eslint[email protected]/node_modules/@eslint/eslintrc/dist/eslintrc.cjs not supported. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings
⏰ Context from checks skipped due to timeout of 90000ms (1)
🔇 Additional comments (6)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
ios/ReactNativeHealthkit.m
(1 hunks)ios/ReactNativeHealthkit.swift
(3 hunks)src/index.ios.tsx
(3 hunks)src/index.native.tsx
(3 hunks)src/native-types.ts
(3 hunks)src/test-setup.ts
(1 hunks)src/utils/subscribeToChanges.ts
(1 hunks)src/utils/workoutSessionMirroringStartHandler.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: eas-ios / eas-build
🔇 Additional comments (17)
src/test-setup.ts (1)
47-47
: LGTM!The mock function for
workoutSessionMirroringStartHandler
is correctly added to the mockModule object, maintaining consistency with other mocks in the test setup.src/index.ios.tsx (1)
43-43
: LGTM!The
workoutSessionMirroringStartHandler
is correctly imported and exported, following the established pattern in the file.Also applies to: 196-196, 276-276
src/utils/subscribeToChanges.ts (1)
11-11
: Enhance type safety
The introduced read-only type annotation ensures immutability and clarifies usage.src/index.native.tsx (3)
106-106
: Add placeholder for workout session mirroring
Returning a resolved promise withfalse
is appropriate for unsupported platforms. Consider verifying or implementing the function for platforms that do support it.
160-160
: Export newly added function
Exposing the function aligns with existing patterns. Confirm that references to this function are only used when the feature is actually supported.
216-216
: Include function in named exports
Ensuring consistency in named exports is beneficial for discoverability. Double-check if the function is being called erroneously on platforms where it's not supported.ios/ReactNativeHealthkit.swift (6)
16-23
: Check bridging approach
Storing_workoutSession
in anAny?
property is a workable solution given iOS 17 availability. Just ensure there's no unintended casting issues in older iOS versions.
695-700
: Add remote workout events
Adding these new event names is consistent with the new workout session mirroring functionality. Confirm that the JavaScript side listens for them.
2248-2269
: Add startWatchAppWithWorkoutConfiguration
This method is well-structured and aligned with iOS 17.0's new API. The error handling approach is consistent with the rest of the code.
2272-2287
: Setup mirroring session
Correctly references theworkoutSessionMirroringStartHandler
. The assignment of_workoutSession
and setting the delegate ensures the events are funneled.
2293-2313
: Handle state changes
This is a clear and appropriate usage ofHKWorkoutSessionDelegate
to dispatch remote workout state changes.
2315-2327
: Handle session errors
Forwarding errors viaonRemoteWorkoutError
is a consistent approach. If there's a chance of sensitive error data, consider redacting.src/native-types.ts (5)
2043-2050
: LGTM! Well-defined workout session states.The enum values are properly defined and match the expected state transitions for HKWorkoutSession.
2052-2056
: LGTM! Well-structured state change event interface.The interface is properly typed with readonly fields and captures all necessary information for state transitions.
2067-2069
: LGTM! Well-structured data received event type.The type properly handles an array of sharable data with readonly fields.
2319-2322
: LGTM! Well-documented native method.The method is properly typed and includes a link to Apple's documentation.
2361-2363
: LGTM! Well-integrated event handling.The new event types and callbacks are properly integrated into the existing event emitter interface.
Also applies to: 2367-2368
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/utils/workoutSessionMirroringStartHandler.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: test
- GitHub Check: typecheck
- GitHub Check: lint
- GitHub Check: eas-ios / eas-build
* @returns {Promise<boolean>} A promise that resolves to true if mirroring started successfully, false otherwise. | ||
* @throws {Error} If there's an error starting the mirroring session. | ||
*/ | ||
const workoutSessionMirroringStartHandler: () => Promise<boolean> = async () => Native.workoutSessionMirroringStartHandler() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider handling errors explicitly.
While the implementation is correct, you might want to add explicit error handling to provide more context when errors occur. This would make debugging easier for consumers of your API.
-const workoutSessionMirroringStartHandler: () => Promise<boolean> = async () => Native.workoutSessionMirroringStartHandler()
+const workoutSessionMirroringStartHandler: () => Promise<boolean> = async () => {
+ try {
+ return await Native.workoutSessionMirroringStartHandler();
+ } catch (error) {
+ console.error('Failed to start workout session mirroring:', error);
+ throw error;
+ }
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const workoutSessionMirroringStartHandler: () => Promise<boolean> = async () => Native.workoutSessionMirroringStartHandler() | |
const workoutSessionMirroringStartHandler: () => Promise<boolean> = async () => { | |
try { | |
return await Native.workoutSessionMirroringStartHandler(); | |
} catch (error) { | |
console.error('Failed to start workout session mirroring:', error); | |
throw error; | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robertherber I think this can be handled directly by the consumer app, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
ios/ReactNativeHealthkit.swift
(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: gentilijuanmanuel
PR: kingstinct/react-native-healthkit#137
File: src/utils/workoutSessionMirroringStartHandler.ts:1-11
Timestamp: 2025-02-25T15:13:50.357Z
Learning: The `workoutSessionMirroringStartHandler` function in React Native Healthkit does not initiate workout mirroring between iOS and watchOS. Instead, it sets up a delegate that will receive and handle workout change notifications from the Apple Watch app to the companion iOS app.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: eas-ios / eas-build
🔇 Additional comments (1)
ios/ReactNativeHealthkit.swift (1)
694-700
: Good job enumerating new events.Including
"onRemoteWorkoutStateChange"
,"onRemoteWorkoutError"
, and"onRemoteWorkoutDataReceived"
insupportedEvents()
cleanly extends the event system for remote workout sessions.
|
||
@available(iOS 17.0.0, *) | ||
@objc(startWatchAppWithWorkoutConfiguration:resolve:reject:) | ||
func startWatchAppWithWorkoutConfiguration( | ||
_ workoutConfiguration: NSDictionary, | ||
resolve: @escaping RCTPromiseResolveBlock, | ||
reject: @escaping RCTPromiseRejectBlock | ||
) { | ||
guard let store = _store else { | ||
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil) | ||
} | ||
|
||
let configuration = parseWorkoutConfiguration(workoutConfiguration) | ||
|
||
store.startWatchApp(with: configuration) { success, error in | ||
if let error { | ||
reject(GENERIC_ERROR, error.localizedDescription, error) | ||
return | ||
} | ||
|
||
resolve(success) | ||
} | ||
} | ||
|
||
@available(iOS 17.0.0, *) | ||
@objc(workoutSessionMirroringStartHandler:reject:) | ||
func workoutSessionMirroringStartHandler( | ||
resolve: @escaping RCTPromiseResolveBlock, | ||
reject: @escaping RCTPromiseRejectBlock | ||
) { | ||
guard let store = _store else { | ||
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil) | ||
} | ||
|
||
store.workoutSessionMirroringStartHandler = { [weak self] mirroringSession in | ||
self?._workoutSession = mirroringSession | ||
self?._workoutSession?.delegate = self | ||
} | ||
|
||
resolve(true) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Validate or handle edge cases for starting the Watch app.
When calling startWatchAppWithWorkoutConfiguration
, consider validating the dictionary keys in workoutConfiguration
or handling scenarios where the Watch might be unreachable. This improves resilience against unexpected configurations and partial failures.
ios/ReactNativeHealthkit.swift
Outdated
// MARK: - HKWorkoutSessionDelegate | ||
|
||
extension ReactNativeHealthkit: HKWorkoutSessionDelegate { | ||
|
||
@available(iOS 17.0.0, *) | ||
func workoutSession( | ||
_ workoutSession: HKWorkoutSession, | ||
didChangeTo toState: HKWorkoutSessionState, | ||
from fromState: HKWorkoutSessionState, | ||
date: Date | ||
) { | ||
Task { @MainActor [weak self] in | ||
guard let self = self else { return } | ||
|
||
if self.bridge != nil && self.bridge.isValid { | ||
self.sendEvent(withName: "onRemoteWorkoutStateChange", body: [ | ||
"toState": toState.rawValue, | ||
"fromState": fromState.rawValue, | ||
"date": self._dateFormatter.string(from: date) | ||
]) | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 17.0.0, *) | ||
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: any Error) { | ||
Task { @MainActor [weak self] in | ||
guard let self = self else { return } | ||
|
||
if self.bridge != nil && self.bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutError", | ||
body: ["error": error.localizedDescription] | ||
) | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 17.0.0, *) | ||
func workoutSession( | ||
_ workoutSession: HKWorkoutSession, | ||
didReceiveDataFromRemoteWorkoutSession data: [Data] | ||
) { | ||
Task { [weak self] in | ||
guard let self = self else { return } | ||
|
||
do { | ||
let serializedData = try data.compactMap { dataItem -> [String: Any]? in | ||
guard let json = try? JSONSerialization.jsonObject(with: dataItem) as? [String: Any] else { | ||
return nil | ||
} | ||
return json | ||
} | ||
|
||
await MainActor.run { [weak self] in | ||
guard let self = self else { return } | ||
|
||
if let bridge = self.bridge, bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutDataReceived", | ||
body: ["data": serializedData] | ||
) | ||
} | ||
} | ||
} catch { | ||
await MainActor.run { [weak self] in | ||
guard let self = self else { return } | ||
|
||
if self.bridge != nil && self.bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutError", | ||
body: ["error": error.localizedDescription] | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Log or handle parsing errors for remote session data.
Within didReceiveDataFromRemoteWorkoutSession
, items failing JSON decoding are quietly skipped. Providing a fallback, logging an error, or sending a separate event for invalid payloads could improve debugging and transparency.
#if os(iOS) | ||
@available(iOS 17.0, *) | ||
var _workoutSession: HKWorkoutSession? { | ||
get { return __session as? HKWorkoutSession } | ||
set { __session = newValue } | ||
} | ||
private var __session: Any? | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider storing the session property with a concrete type.
The private property __session
is declared as Any?
. Unless there is a specific need to treat the stored object generically, using HKWorkoutSession?
directly would improve clarity and remove the need for casting in _workoutSession
.
#if os(iOS)
@available(iOS 17.0, *)
-var _workoutSession: HKWorkoutSession? {
- get { return __session as? HKWorkoutSession }
- set { __session = newValue }
-}
-private var __session: Any?
+var _workoutSession: HKWorkoutSession?
#endif
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
#if os(iOS) | |
@available(iOS 17.0, *) | |
var _workoutSession: HKWorkoutSession? { | |
get { return __session as? HKWorkoutSession } | |
set { __session = newValue } | |
} | |
private var __session: Any? | |
#endif | |
#if os(iOS) | |
@available(iOS 17.0, *) | |
var _workoutSession: HKWorkoutSession? | |
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
ios/ReactNativeHealthkit.swift
(3 hunks)src/native-types.ts
(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: gentilijuanmanuel
PR: kingstinct/react-native-healthkit#137
File: src/utils/workoutSessionMirroringStartHandler.ts:1-11
Timestamp: 2025-02-25T15:13:50.357Z
Learning: The `workoutSessionMirroringStartHandler` function in React Native Healthkit does not initiate workout mirroring between iOS and watchOS. Instead, it sets up a delegate that will receive and handle workout change notifications from the Apple Watch app to the companion iOS app.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: eas-ios / eas-build
🔇 Additional comments (14)
ios/ReactNativeHealthkit.swift (7)
16-23
: Consider simplifying session storageStoring
_workoutSession
directly asHKWorkoutSession?
may remove the need to maintain__session
asAny?
and avoid repeated casts. If there’s no specific legacy constraint requiring the generic property, you can simplify by removing it.
695-701
: New remote workout events look goodThese additions cleanly expand the available event types for remote workout interactions, following a consistent naming pattern.
2248-2249
: No functional code hereThese lines appear to be empty or spacing changes with no observable impact.
2292-2293
: Delegate marker commentJust a new section marker with no direct logic changes.
2294-2295
: New extension for HKWorkoutSessionDelegateDeclaring the extension is straightforward. Implementation details follow.
2296-2314
: Session state change handling looks goodThe code correctly captures the state changes and emits them to JavaScript. This straightforward approach leverages background tasks to update the main actor if necessary.
2316-2328
: Error handling is appropriately routedYou send an “onRemoteWorkoutError” event whenever an error occurs, which is consistent with the other delegate callbacks.
src/native-types.ts (7)
2043-2053
: Enum values look good.Defining numeric values for each session state is appropriate, as it aligns well with underlying native constants. This approach preserves consistency and facilitates easier comparisons or references in bridging code.
2055-2067
: Enum event definitions appear consistent.These event types match their native counterparts, ensuring clear mapping from HealthKit’s delegate callbacks.
2075-2077
: Repeating previous suggestion on more structured errors.A single string for
error
reduces the ability to offer programmatic error handling. Consider returning a structured object with a code, message, and domain for better clarity.
2079-2082
: Retaining flexibility for arbitrary payloads.Users may benefit from a discriminated union for stronger type safety, but since you decided to allow custom dictionaries, this remains fine.
2084-2086
: Data reception event is well-defined.No issues found. The read-only array constraint ensures immutability of received data, which is a good practice.
2088-2090
: Straightforward event wrapper.The property
type
referencing the newWorkoutSessionEventType
is suitably typed, ensuring clarity for event consumers.
2389-2390
: Event registration signature is coherent.The function overload neatly accommodates new event types, preserving clarity. Great job extending your event model!
@objc(startWatchAppWithWorkoutConfiguration:resolve:reject:) | ||
func startWatchAppWithWorkoutConfiguration( | ||
_ workoutConfiguration: NSDictionary, | ||
resolve: @escaping RCTPromiseResolveBlock, | ||
reject: @escaping RCTPromiseRejectBlock | ||
) { | ||
guard let store = _store else { | ||
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil) | ||
} | ||
|
||
let configuration = parseWorkoutConfiguration(workoutConfiguration) | ||
|
||
store.startWatchApp(with: configuration) { success, error in | ||
if let error { | ||
reject(GENERIC_ERROR, error.localizedDescription, error) | ||
return | ||
} | ||
|
||
resolve(success) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Ensure thorough error handling when starting the Watch app
You correctly reject the promise on error. Consider supplementing errors with extra context or user-facing instructions if the Watch is inactive or unreachable. This can help users diagnose potential setup issues more easily.
|
||
@available(iOS 17.0.0, *) | ||
@objc(workoutSessionMirroringStartHandler:reject:) | ||
func workoutSessionMirroringStartHandler( | ||
resolve: @escaping RCTPromiseResolveBlock, | ||
reject: @escaping RCTPromiseRejectBlock | ||
) { | ||
guard let store = _store else { | ||
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil) | ||
} | ||
|
||
store.workoutSessionMirroringStartHandler = { [weak self] mirroringSession in | ||
self?._workoutSession = mirroringSession | ||
self?._workoutSession?.delegate = self | ||
} | ||
|
||
resolve(true) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Validate mirroring start logic
This handler succeeds immediately by returning true
without waiting for confirmation that mirroring actually began. If you need to confirm a session was established successfully, consider providing a success/failure callback from the property’s closure. Also note that if multiple calls override store.workoutSessionMirroringStartHandler
, you’ll want to ensure that’s your intended behavior.
@available(iOS 17.0.0, *) | ||
func workoutSession( | ||
_ workoutSession: HKWorkoutSession, | ||
didReceiveDataFromRemoteWorkoutSession data: [Data] | ||
) { | ||
Task { [weak self] in | ||
guard let self = self else { return } | ||
|
||
do { | ||
let serializedData = try data.compactMap { dataItem -> [String: Any]? in | ||
guard let json = try? JSONSerialization.jsonObject(with: dataItem) as? [String: Any] else { | ||
return nil | ||
} | ||
return json | ||
} | ||
|
||
await MainActor.run { [weak self] in | ||
guard let self = self else { return } | ||
|
||
if let bridge = self.bridge, bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutDataReceived", | ||
body: ["data": serializedData] | ||
) | ||
} | ||
} | ||
} catch { | ||
await MainActor.run { [weak self] in | ||
guard let self = self else { return } | ||
|
||
if self.bridge != nil && self.bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutError", | ||
body: ["error": error.localizedDescription] | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider partial error handling for JSON parsing
Currently, individual data items failing JSON decoding are silently skipped. If you need greater visibility, consider logging or sending a separate error event for those partial failures to aid debugging.
@available(iOS 17.0, *) | ||
func workoutSession(_ workoutSession: HKWorkoutSession, didGenerate event: HKWorkoutEvent) { | ||
if self.bridge != nil && self.bridge.isValid { | ||
self.sendEvent( | ||
withName: "onRemoteWorkoutEventReceived", | ||
body: ["type": event.type.rawValue] | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Provide more event details if needed
Right now, only the event type is sent. If you need to expose finer-grained metadata (e.g., event timestamps or reasons), consider sending additional fields to better inform the JS layer.
export interface WorkoutStateChangeEvent { | ||
readonly toState: WorkoutSessionState; | ||
readonly fromState: WorkoutSessionState; | ||
readonly date: string; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider using a stronger type for date
.
If possible, using an ISO 8601 string type (e.g., ISODateString
) or a Date
object can help with type safety and clarity, ensuring consumers can parse it easily.
/** | ||
* @see {@link https://developer.apple.com/documentation/healthkit/hkhealthstore/4172878-workoutsessionmirroringstarthand Apple Docs } | ||
*/ | ||
readonly workoutSessionMirroringStartHandler: () => Promise<boolean>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Confirm error handling for mirrored sessions.
Returning a boolean may not be sufficient in scenarios where watchOS isn’t paired or available. Consider an alternative return value or additional detail (e.g., an error code) to enhance debugging.
type OnRemoteWorkoutStateChangeCallback = (event: WorkoutStateChangeEvent) => void; | ||
type OnRemoteWorkoutErrorCallback = (event: WorkoutErrorEvent) => void; | ||
type OnRemoteWorkoutDataCallback = (event: WorkoutDataReceivedEvent) => void; | ||
type OnRemoteWorkoutEventReceivedCallback = (event: WorkoutEventReceivedEvent) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Callback definitions look neat.
Adding brief doc comments to each callback could improve maintainability and guide integrators, especially around expected parameter shapes and usage contexts.
Expo preview is ready for review. There should soon be a new native build available here |
Context
This PR, in addition to this one, completes the support to Workout Mirroring between an iOS app and its corresponding watchOS app, feature described in this WWDC episode.
Summary of changes
I added support two main APIs:
workoutSessionMirroringStartHandler
: method to set up the delegate that will react to remote workout session event updates. Official documentation can be found here.HKWorkoutSessionDelegate
methods:workoutSession(_:didChangeTo:from:date:)
: to react to workout session state changes (paused, resumed, ended). Official documentation can be found here.workoutSession(_:didFailWithError:)
: to react to possible errors that can happen during a workout session. Official documentation can be found here.workoutSession(_:didReceiveDataFromRemoteWorkoutSession:)
: to react to data that's coming from the workout session (like workout metrics). Official documentation can be found here.Every time we get updates from delegate methods, we emit an event using the
EventEmitter
, and consumer apps will be able to listen to them and perform actions accordingly.Summary by CodeRabbit
New Features
Bug Fixes
Tests