react-native-workouts 0.2.1 β†’ 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,42 +1,25 @@
1
+ ![react-native-workouts-banner](./public/react-native-workouts-banner.png)
2
+
1
3
  # react-native-workouts
2
4
 
3
- React Native Expo module for Apple WorkoutKit - create, preview, and sync custom
5
+ πŸ‹οΈβ€β™€οΈ React Native Expo module for Apple WorkoutKit β€” create, preview, and sync
4
6
  workouts to Apple Watch.
5
7
 
6
8
  ## Features
7
9
 
8
- - Create custom interval workouts with warmup, work/recovery intervals, and
9
- cooldown
10
- - Create single-goal workouts (distance, time, or energy based)
11
- - Create pacer workouts with speed or pace targets
12
- - Schedule workouts to sync with Apple Watch Workout app
13
- - Full TypeScript support with comprehensive type definitions
14
- - Supports all major workout activity types
15
-
16
- ## API Features
17
-
18
- ### Workout Creation
19
-
20
- - **Custom Workouts** - Complex interval workouts with warmup, blocks
21
- (iterations, work/recovery steps), cooldown, and alerts
22
- - **Single Goal Workouts** - Simple distance, time, or energy-based workouts
23
- - **Pacer Workouts** - Speed or pace target workouts
24
- - **Preview Workouts** - Present Apple’s system workout preview modal (includes
25
- add/send to Apple Watch)
26
-
27
- ### Scheduling
28
-
29
- - **scheduleWorkout()** - Schedule custom workouts to Apple Watch
30
- - **scheduleSingleGoalWorkout()** - Schedule single goal workouts
31
- - **schedulePacerWorkout()** - Schedule pacer workouts
32
- - **getScheduledWorkouts()** - List all scheduled workouts
33
- - **removeScheduledWorkout(id)** - Remove specific workout
34
- - **removeAllScheduledWorkouts()** - Clear all scheduled workouts
10
+ - ✨ Create custom interval workouts (warmup, blocks, cooldown, alerts)
11
+ - 🎯 Create single-goal workouts (distance / time / energy)
12
+ - πŸƒβ€β™‚οΈ Create pacer workouts (pace / speed targets)
13
+ - 🧩 Create multisport Swim / Bike / Run workouts
14
+ - πŸ‘€ Preview with Apple’s system UI (includes β€œAdd to Watch” / β€œSend to Watch”)
15
+ - ⌚️ Schedule & sync to the Apple Watch Workout app
16
+ - 🧠 Hooks-first API + stateful `WorkoutPlan` handle (Expo Shared Object)
17
+ - βœ… Full TypeScript support
35
18
 
36
19
  ## Requirements
37
20
 
38
- - iOS 17.0+
39
- - Expo SDK 54+
21
+ - iOS 17.0+ **at runtime** (WorkoutKit)
22
+ - Tested with Expo SDK 54+
40
23
  - Apple Watch paired with iPhone (for workout sync)
41
24
 
42
25
  ## Installation
@@ -45,18 +28,8 @@ workouts to Apple Watch.
45
28
  npm install react-native-workouts
46
29
  # or
47
30
  yarn add react-native-workouts
48
- ```
49
-
50
- ### Expo Configuration
51
-
52
- Add the module to your app.json:
53
-
54
- ```json
55
- {
56
- "expo": {
57
- "plugins": ["react-native-workouts"]
58
- }
59
- }
31
+ # or
32
+ pnpm add react-native-workouts
60
33
  ```
61
34
 
62
35
  ### Info.plist Configuration
@@ -72,85 +45,84 @@ Add the following keys to your Info.plist for HealthKit access:
72
45
 
73
46
  ## Usage
74
47
 
75
- ### Authorization
48
+ ### πŸ” Authorization (hook)
76
49
 
77
- Before scheduling workouts, you need to request authorization:
50
+ Before previewing/scheduling, request authorization (HealthKit / WorkoutKit):
78
51
 
79
52
  ```typescript
80
- import ReactNativeWorkouts from "react-native-workouts";
81
-
82
- // Check current authorization status
83
- const status = await ReactNativeWorkouts.getAuthorizationStatus();
84
- // Returns: 'authorized' | 'notDetermined' | 'denied' | 'unknown'
85
-
86
- // Request authorization
87
- const newStatus = await ReactNativeWorkouts.requestAuthorization();
88
- ```
89
-
90
- ### Object-oriented API (WorkoutPlan + Hooks)
53
+ import { useWorkoutAuthorization } from "react-native-workouts";
91
54
 
92
- This package also exposes a **stateful `WorkoutPlan` shared object** (Expo
93
- Shared Object) that you can keep around in JS and call methods on it:
55
+ export function MyScreen() {
56
+ const { status, request, isLoading, error } = useWorkoutAuthorization();
94
57
 
95
- - `plan.preview()` β€” shows Apple's system preview modal (includes send/add to
96
- Apple Watch)
97
- - `plan.scheduleAndSync(date)` β€” **schedules** the plan using Apple's
98
- `WorkoutScheduler` (this is how the plan is synced to the Apple Watch Workout
99
- app)
100
- - `plan.export()` β€” returns `{ id, kind, config }` so you can **persist/share**
101
- the plan in your own backend
58
+ // status: 'authorized' | 'notDetermined' | 'denied' | 'unknown' | null
59
+ // request(): prompts the system dialog (if needed) and returns the new status
102
60
 
103
- #### What `plan.export()` means
104
-
105
- `plan.export()` does **not** export a `.workout` file or something the system
106
- can import.
61
+ return null;
62
+ }
63
+ ```
107
64
 
108
- It returns a small JSON payload:
65
+ ### πŸš€ Quick start (hooks-first `WorkoutPlan`)
109
66
 
110
- - `id`: the UUID of the `WorkoutPlan` instance
111
- - `kind`: `"custom" | "singleGoal" | "pacer" | "swimBikeRun"`
112
- - `config`: the original config used to create the plan (so you can store it and
113
- recreate the plan later)
67
+ Hooks create a **stateful `WorkoutPlan` shared object** (Expo Shared Object).
68
+ You call methods on the plan:
114
69
 
115
- You typically create a plan via hooks:
70
+ - `plan.preview()` β€” opens Apple’s workout preview UI (includes β€œAdd/Send to
71
+ Watch”)
72
+ - `plan.scheduleAndSync(date)` β€” schedules using Apple’s `WorkoutScheduler`
73
+ (this is what syncs it to the Watch)
74
+ - `plan.export()` β€” returns `{ id, kind, config }` so you can persist/share and
75
+ recreate later
116
76
 
117
77
  ```typescript
118
- import ReactNativeWorkouts, {
78
+ import { useMemo } from "react";
79
+ import {
119
80
  type CustomWorkoutConfig,
120
81
  useCustomWorkout,
82
+ useWorkoutAuthorization,
121
83
  } from "react-native-workouts";
122
84
 
123
- const config: CustomWorkoutConfig = {
124
- activityType: "running",
125
- locationType: "outdoor",
126
- displayName: "Morning Intervals",
127
- blocks: [
128
- {
129
- iterations: 4,
130
- steps: [
131
- {
132
- purpose: "work",
133
- goal: { type: "distance", value: 400, unit: "meters" },
134
- },
85
+ export function MyWorkoutScreen() {
86
+ const { status, request } = useWorkoutAuthorization();
87
+
88
+ const config = useMemo<CustomWorkoutConfig>(
89
+ () => ({
90
+ activityType: "running",
91
+ locationType: "outdoor",
92
+ displayName: "Morning Intervals",
93
+ warmup: { goal: { type: "time", value: 5, unit: "minutes" } },
94
+ blocks: [
135
95
  {
136
- purpose: "recovery",
137
- goal: { type: "time", value: 90, unit: "seconds" },
96
+ iterations: 4,
97
+ steps: [
98
+ {
99
+ purpose: "work",
100
+ goal: { type: "distance", value: 400, unit: "meters" },
101
+ alert: { type: "pace", min: 4, max: 5, unit: "min/km" },
102
+ },
103
+ {
104
+ purpose: "recovery",
105
+ goal: { type: "time", value: 90, unit: "seconds" },
106
+ },
107
+ ],
138
108
  },
139
109
  ],
140
- },
141
- ],
142
- };
110
+ cooldown: { goal: { type: "time", value: 5, unit: "minutes" } },
111
+ }),
112
+ [],
113
+ );
143
114
 
144
- export function MyWorkoutScreen() {
145
115
  const { plan, isLoading, error } = useCustomWorkout(config);
146
116
 
147
117
  const preview = async () => {
148
118
  if (!plan) return;
119
+ if (status !== "authorized") await request();
149
120
  await plan.preview();
150
121
  };
151
122
 
152
- const scheduleAndSyncTomorrow = async () => {
123
+ const scheduleTomorrowMorning = async () => {
153
124
  if (!plan) return;
125
+ if (status !== "authorized") await request();
154
126
  await plan.scheduleAndSync({
155
127
  year: 2026,
156
128
  month: 1,
@@ -164,191 +136,100 @@ export function MyWorkoutScreen() {
164
136
  }
165
137
  ```
166
138
 
167
- ### Custom Interval Workout
168
-
169
- Create complex interval workouts with warmup, multiple blocks, and cooldown:
170
-
171
- ```typescript
172
- import ReactNativeWorkouts from "react-native-workouts";
173
- import type { CustomWorkoutConfig } from "react-native-workouts";
174
-
175
- const workout: CustomWorkoutConfig = {
176
- activityType: "running",
177
- locationType: "outdoor",
178
- displayName: "Morning Intervals",
179
- warmup: {
180
- goal: { type: "time", value: 5, unit: "minutes" },
181
- },
182
- blocks: [
183
- {
184
- iterations: 4,
185
- steps: [
186
- {
187
- purpose: "work",
188
- goal: { type: "distance", value: 400, unit: "meters" },
189
- alert: { type: "pace", min: 4, max: 5, unit: "min/km" },
190
- },
191
- {
192
- purpose: "recovery",
193
- goal: { type: "time", value: 90, unit: "seconds" },
194
- },
195
- ],
196
- },
197
- ],
198
- cooldown: {
199
- goal: { type: "time", value: 5, unit: "minutes" },
200
- },
201
- };
202
-
203
- // Validate the workout
204
- const result = await ReactNativeWorkouts.createCustomWorkout(workout);
205
- console.log(result.valid); // true
206
-
207
- // Preview (system modal with "Add to Watch"/"Send to Watch")
208
- await ReactNativeWorkouts.previewWorkout(workout);
209
-
210
- // Schedule for tomorrow at 7 AM
211
- const tomorrow = new Date();
212
- tomorrow.setDate(tomorrow.getDate() + 1);
213
-
214
- const scheduleResult = await ReactNativeWorkouts.scheduleWorkout(workout, {
215
- year: tomorrow.getFullYear(),
216
- month: tomorrow.getMonth() + 1,
217
- day: tomorrow.getDate(),
218
- hour: 7,
219
- minute: 0,
220
- });
221
-
222
- console.log(scheduleResult.id); // UUID of scheduled workout
223
- ```
224
-
225
- ### Single Goal Workout
226
-
227
- Create simple goal-based workouts:
139
+ ### 🎯 Single goal workouts (hook)
228
140
 
229
141
  ```typescript
230
- import ReactNativeWorkouts from "react-native-workouts";
142
+ import { useMemo } from "react";
231
143
  import type { SingleGoalWorkoutConfig } from "react-native-workouts";
232
-
233
- // 5K run
234
- const fiveK: SingleGoalWorkoutConfig = {
235
- activityType: "running",
236
- locationType: "outdoor",
237
- displayName: "5K Run",
238
- goal: { type: "distance", value: 5, unit: "kilometers" },
239
- };
240
-
241
- // 30 minute cycling session
242
- const cycling: SingleGoalWorkoutConfig = {
243
- activityType: "cycling",
244
- locationType: "indoor",
245
- displayName: "30 Min Ride",
246
- goal: { type: "time", value: 30, unit: "minutes" },
247
- };
248
-
249
- // 500 calorie workout
250
- const calorieBurn: SingleGoalWorkoutConfig = {
251
- activityType: "highIntensityIntervalTraining",
252
- displayName: "Calorie Burner",
253
- goal: { type: "energy", value: 500, unit: "kilocalories" },
254
- };
255
-
256
- await ReactNativeWorkouts.createSingleGoalWorkout(fiveK);
257
-
258
- // Preview
259
- await ReactNativeWorkouts.previewSingleGoalWorkout(fiveK);
144
+ import { useSingleGoalWorkout } from "react-native-workouts";
145
+
146
+ export function FiveKScreen() {
147
+ const config = useMemo<SingleGoalWorkoutConfig>(
148
+ () => ({
149
+ activityType: "running",
150
+ locationType: "outdoor",
151
+ displayName: "5K Run",
152
+ goal: { type: "distance", value: 5, unit: "kilometers" },
153
+ }),
154
+ [],
155
+ );
156
+
157
+ const { plan } = useSingleGoalWorkout(config);
158
+ return null;
159
+ }
260
160
  ```
261
161
 
262
- ### Pacer Workout
263
-
264
- Create pace-based workouts:
162
+ ### πŸƒβ€β™‚οΈ Pacer workouts (hook)
265
163
 
266
164
  ```typescript
267
- import ReactNativeWorkouts from "react-native-workouts";
165
+ import { useMemo } from "react";
268
166
  import type { PacerWorkoutConfig } from "react-native-workouts";
269
-
270
- const pacerWorkout: PacerWorkoutConfig = {
271
- activityType: "running",
272
- locationType: "outdoor",
273
- displayName: "Tempo Run",
274
- target: {
275
- type: "pace",
276
- value: 5, // 5 minutes per kilometer
277
- unit: "min/km",
278
- },
279
- };
280
-
281
- await ReactNativeWorkouts.createPacerWorkout(pacerWorkout);
282
-
283
- // Preview
284
- await ReactNativeWorkouts.previewPacerWorkout(pacerWorkout);
167
+ import { usePacerWorkout } from "react-native-workouts";
168
+
169
+ export function TempoRunScreen() {
170
+ const config = useMemo<PacerWorkoutConfig>(
171
+ () => ({
172
+ activityType: "running",
173
+ locationType: "outdoor",
174
+ displayName: "Tempo Run",
175
+ target: { type: "pace", value: 5, unit: "min/km" },
176
+ }),
177
+ [],
178
+ );
179
+
180
+ const { plan } = usePacerWorkout(config);
181
+ return null;
182
+ }
285
183
  ```
286
184
 
287
- ### Swim / Bike / Run (Multisport) Workout
288
-
289
- Create a multisport workout (triathlon-like) with an ordered list of activities:
185
+ ### 🧩 Swim / Bike / Run (multisport) (hook)
290
186
 
291
187
  ```typescript
188
+ import { useMemo } from "react";
292
189
  import type { SwimBikeRunWorkoutConfig } from "react-native-workouts";
190
+ import { useSwimBikeRunWorkout } from "react-native-workouts";
191
+
192
+ export function TriathlonScreen() {
193
+ const config = useMemo<SwimBikeRunWorkoutConfig>(
194
+ () => ({
195
+ displayName: "Sprint Triathlon",
196
+ activities: [
197
+ { type: "swimming", locationType: "indoor" },
198
+ { type: "cycling", locationType: "outdoor" },
199
+ { type: "running", locationType: "outdoor" },
200
+ ],
201
+ }),
202
+ [],
203
+ );
293
204
 
294
- const triathlon: SwimBikeRunWorkoutConfig = {
295
- displayName: "Sprint Triathlon",
296
- activities: [
297
- { type: "swimming", locationType: "indoor" },
298
- { type: "cycling", locationType: "outdoor" },
299
- { type: "running", locationType: "outdoor" },
300
- ],
301
- };
302
-
303
- // Create a plan handle (recommended)
304
- const plan = await ReactNativeWorkouts.createSwimBikeRunWorkoutPlan(triathlon);
305
-
306
- // Preview
307
- await plan.preview();
205
+ const { plan } = useSwimBikeRunWorkout(config);
206
+ return null;
207
+ }
308
208
  ```
309
209
 
310
- ### Managing Scheduled Workouts
311
-
312
- ```typescript
313
- // Get all scheduled workouts
314
- const workouts = await ReactNativeWorkouts.getScheduledWorkouts();
315
-
316
- // Remove a specific workout
317
- await ReactNativeWorkouts.removeScheduledWorkout(workouts[0].id);
210
+ ### πŸ“¦ Persist / share a plan (`plan.export()`)
318
211
 
319
- // Remove all scheduled workouts
320
- await ReactNativeWorkouts.removeAllScheduledWorkouts();
321
- ```
322
-
323
- ### Check Goal Support
212
+ `plan.export()` does **not** export a `.workout` file.
324
213
 
325
- Verify if a goal type is supported for an activity:
214
+ It returns a small JSON payload you can store in your backend and later
215
+ recreate:
326
216
 
327
- ```typescript
328
- const supported = await ReactNativeWorkouts.supportsGoal(
329
- "swimming",
330
- "indoor",
331
- "distance",
332
- );
333
- ```
217
+ - `id`: UUID of the `WorkoutPlan` instance
218
+ - `kind`: `"custom" | "singleGoal" | "pacer" | "swimBikeRun"`
219
+ - `config`: the original config used to create the plan
334
220
 
335
- ### Utility Functions
221
+ ### πŸ“… Managing scheduled workouts (hook)
336
222
 
337
223
  ```typescript
338
- // Get all supported activity types
339
- const activities = ReactNativeWorkouts.getSupportedActivityTypes();
340
- // ['running', 'cycling', 'walking', 'hiking', ...]
341
-
342
- // Get supported goal types
343
- const goals = ReactNativeWorkouts.getSupportedGoalTypes();
344
- // ['open', 'distance', 'time', 'energy']
224
+ import { useScheduledWorkouts } from "react-native-workouts";
345
225
 
346
- // Get supported location types
347
- const locations = ReactNativeWorkouts.getSupportedLocationTypes();
348
- // ['indoor', 'outdoor']
226
+ export function ScheduledWorkoutsScreen() {
227
+ const { workouts, remove, removeAll, schedule, isLoading, error } =
228
+ useScheduledWorkouts();
349
229
 
350
- // Check if HealthKit is available
351
- const available = ReactNativeWorkouts.isAvailable;
230
+ // schedule(plan, date) calls plan.scheduleAndSync(date) and reloads the list
231
+ return null;
232
+ }
352
233
  ```
353
234
 
354
235
  ## API Reference
@@ -444,12 +325,23 @@ type PaceUnit = "minutesPerKilometer" | "min/km" | "minutesPerMile" | "min/mi";
444
325
 
445
326
  - Swimming workouts do not support custom intervals, use SingleGoalWorkout
446
327
  instead
447
- - Not all activity types support all goal types - use `supportsGoal()` to check
448
328
  - Scheduled workouts appear in the Workout app on Apple Watch and Fitness app on
449
329
  iPhone
450
330
  - Workouts display your app's icon and name in the Workout app
451
331
  - The preview APIs use Apple’s system UI (`workoutPreview`) and require iOS 17+
452
332
 
333
+ ### Deployment target note
334
+
335
+ This library is built on Apple's `WorkoutKit` (introduced in iOS 17). To avoid
336
+ forcing apps to raise their deployment target just to install the package, the
337
+ iOS native code:
338
+
339
+ - **weak-links** `WorkoutKit`
340
+ - gates all exported APIs behind `#available(iOS 17.0, *)`
341
+
342
+ On **iOS < 17**, calling any WorkoutKit API will reject/throw with an
343
+ `Unavailable` error.
344
+
453
345
  ## License
454
346
 
455
347
  MIT
package/app.json CHANGED
@@ -1,5 +1,3 @@
1
1
  {
2
- "plugins": [
3
- "expo-build-properties"
4
- ]
5
- }
2
+ "plugins": []
3
+ }
@@ -1,5 +1,5 @@
1
1
  import { NativeModule } from "expo";
2
- import type { ActivityType, AuthorizationStatus, CustomWorkoutConfig, DateComponents, LocationType, PacerWorkoutConfig, SwimBikeRunWorkoutConfig, ReactNativeWorkoutsModuleEvents, ScheduledWorkout, ScheduleResult, SingleGoalWorkoutConfig, WorkoutPlan, WorkoutValidationResult } from "./ReactNativeWorkouts.types";
2
+ import type { ActivityType, AuthorizationStatus, CustomWorkoutConfig, DateComponents, LocationType, PacerWorkoutConfig, ReactNativeWorkoutsModuleEvents, ScheduledWorkout, ScheduleResult, SingleGoalWorkoutConfig, SwimBikeRunWorkoutConfig, WorkoutPlan, WorkoutValidationResult } from "./ReactNativeWorkouts.types";
3
3
  declare class ReactNativeWorkoutsModule extends NativeModule<ReactNativeWorkoutsModuleEvents> {
4
4
  /**
5
5
  * Whether Health data is available on this device.
@@ -29,7 +29,7 @@ declare class ReactNativeWorkoutsModule extends NativeModule<ReactNativeWorkouts
29
29
  /**
30
30
  * Schedules a custom workout (syncs it to the Apple Watch Workout app).
31
31
  *
32
- * Prefer using `createCustomWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
32
+ * Prefer using `useCustomWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
33
33
  */
34
34
  scheduleWorkout(config: CustomWorkoutConfig, date: DateComponents): Promise<ScheduleResult>;
35
35
  /**
@@ -43,7 +43,7 @@ declare class ReactNativeWorkoutsModule extends NativeModule<ReactNativeWorkouts
43
43
  /**
44
44
  * Schedules a single-goal workout (syncs it to the Apple Watch Workout app).
45
45
  *
46
- * Prefer using `createSingleGoalWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
46
+ * Prefer using `useSingleGoalWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
47
47
  */
48
48
  scheduleSingleGoalWorkout(config: SingleGoalWorkoutConfig, date: DateComponents): Promise<ScheduleResult>;
49
49
  /**
@@ -57,7 +57,7 @@ declare class ReactNativeWorkoutsModule extends NativeModule<ReactNativeWorkouts
57
57
  /**
58
58
  * Schedules a pacer workout (syncs it to the Apple Watch Workout app).
59
59
  *
60
- * Prefer using `createPacerWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
60
+ * Prefer using `usePacerWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
61
61
  */
62
62
  schedulePacerWorkout(config: PacerWorkoutConfig, date: DateComponents): Promise<ScheduleResult>;
63
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeWorkoutsModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeWorkoutsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,wBAAwB,EACxB,+BAA+B,EAC/B,gBAAgB,EAChB,cAAc,EACd,uBAAuB,EACvB,WAAW,EACX,uBAAuB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,OAAO,yBACZ,SAAQ,YAAY,CAAC,+BAA+B,CAAC;IAErD;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAG9B;;OAEG;IACH,sBAAsB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IACtD;;OAEG;IACH,oBAAoB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAGpD;;OAEG;IACH,YAAY,CACV,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IAGnB;;OAEG;IACH,mBAAmB,CACjB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAC7D;;;;OAIG;IACH,eAAe,CACb,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,uBAAuB,CACrB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAC3E;;;;OAIG;IACH,yBAAyB,CACvB,MAAM,EAAE,uBAAuB,EAC/B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,kBAAkB,CAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IACjE;;;;OAIG;IACH,oBAAoB,CAClB,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,uBAAuB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IAC1E;;OAEG;IACH,2BAA2B,CACzB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC;IACvB;;OAEG;IACH,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IACxE;;OAEG;IACH,4BAA4B,CAC1B,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAGvB;;OAEG;IACH,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IACnD;;OAEG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACpD;;OAEG;IACH,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC;IAG9C,yBAAyB,IAAI,YAAY,EAAE;IAC3C,qBAAqB,IAAI,MAAM,EAAE;IACjC,yBAAyB,IAAI,YAAY,EAAE;CAC5C;;AAED,wBAEE"}
1
+ {"version":3,"file":"ReactNativeWorkoutsModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeWorkoutsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,+BAA+B,EAC/B,gBAAgB,EAChB,cAAc,EACd,uBAAuB,EACvB,wBAAwB,EACxB,WAAW,EACX,uBAAuB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,OAAO,yBACZ,SAAQ,YAAY,CAAC,+BAA+B,CAAC;IAErD;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAG9B;;OAEG;IACH,sBAAsB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IACtD;;OAEG;IACH,oBAAoB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAGpD;;OAEG;IACH,YAAY,CACV,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IAGnB;;OAEG;IACH,mBAAmB,CACjB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAC7D;;;;OAIG;IACH,eAAe,CACb,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,uBAAuB,CACrB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,wBAAwB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAC3E;;;;OAIG;IACH,yBAAyB,CACvB,MAAM,EAAE,uBAAuB,EAC/B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,kBAAkB,CAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,uBAAuB,CAAC;IACnC;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IACjE;;;;OAIG;IACH,oBAAoB,CAClB,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC;IAG1B;;OAEG;IACH,uBAAuB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;IAC1E;;OAEG;IACH,2BAA2B,CACzB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC;IACvB;;OAEG;IACH,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IACxE;;OAEG;IACH,4BAA4B,CAC1B,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAGvB;;OAEG;IACH,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IACnD;;OAEG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACpD;;OAEG;IACH,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC;IAG9C,yBAAyB,IAAI,YAAY,EAAE;IAC3C,qBAAqB,IAAI,MAAM,EAAE;IACjC,yBAAyB,IAAI,YAAY,EAAE;CAC5C;;AAED,wBAEE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeWorkoutsModule.js","sourceRoot":"","sources":["../src/ReactNativeWorkoutsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAwJzD,eAAe,mBAAmB,CAChC,qBAAqB,CACtB,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport type {\n ActivityType,\n AuthorizationStatus,\n CustomWorkoutConfig,\n DateComponents,\n LocationType,\n PacerWorkoutConfig,\n SwimBikeRunWorkoutConfig,\n ReactNativeWorkoutsModuleEvents,\n ScheduledWorkout,\n ScheduleResult,\n SingleGoalWorkoutConfig,\n WorkoutPlan,\n WorkoutValidationResult,\n} from \"./ReactNativeWorkouts.types\";\n\ndeclare class ReactNativeWorkoutsModule\n extends NativeModule<ReactNativeWorkoutsModuleEvents> {\n // Constants\n /**\n * Whether Health data is available on this device.\n * On iOS simulators this is typically `false`.\n */\n readonly isAvailable: boolean;\n\n // Authorization\n /**\n * Returns the current WorkoutKit authorization status.\n */\n getAuthorizationStatus(): Promise<AuthorizationStatus>;\n /**\n * Prompts the user for WorkoutKit authorization (if needed).\n */\n requestAuthorization(): Promise<AuthorizationStatus>;\n\n // Validation\n /**\n * Returns whether a given goal type is supported for the provided activity + location.\n */\n supportsGoal(\n activityType: ActivityType,\n locationType: LocationType,\n goalType: string,\n ): Promise<boolean>;\n\n // Custom Workouts\n /**\n * Validates a custom workout config. (Legacy name: this does not persist anything.)\n */\n createCustomWorkout(\n config: CustomWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a custom workout via Apple's system modal.\n */\n previewWorkout(config: CustomWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a custom workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `createCustomWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n scheduleWorkout(\n config: CustomWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Single Goal Workouts\n /**\n * Validates a single-goal workout config. (Legacy name: this does not persist anything.)\n */\n createSingleGoalWorkout(\n config: SingleGoalWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a single-goal workout via Apple's system modal.\n */\n previewSingleGoalWorkout(config: SingleGoalWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a single-goal workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `createSingleGoalWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n scheduleSingleGoalWorkout(\n config: SingleGoalWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Pacer Workouts\n /**\n * Validates a pacer workout config. (Legacy name: this does not persist anything.)\n */\n createPacerWorkout(\n config: PacerWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a pacer workout via Apple's system modal.\n */\n previewPacerWorkout(config: PacerWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a pacer workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `createPacerWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n schedulePacerWorkout(\n config: PacerWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Shared WorkoutPlan factories (object-oriented API)\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createCustomWorkoutPlan(config: CustomWorkoutConfig): Promise<WorkoutPlan>;\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createSingleGoalWorkoutPlan(\n config: SingleGoalWorkoutConfig,\n ): Promise<WorkoutPlan>;\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createPacerWorkoutPlan(config: PacerWorkoutConfig): Promise<WorkoutPlan>;\n /**\n * Creates a stateful multisport `WorkoutPlan` shared object (recommended API for new code).\n */\n createSwimBikeRunWorkoutPlan(\n config: SwimBikeRunWorkoutConfig,\n ): Promise<WorkoutPlan>;\n\n // Scheduled Workouts Management\n /**\n * Lists scheduled workouts created by this app.\n */\n getScheduledWorkouts(): Promise<ScheduledWorkout[]>;\n /**\n * Removes a scheduled workout by ID.\n */\n removeScheduledWorkout(id: string): Promise<boolean>;\n /**\n * Removes all scheduled workouts created by this app.\n */\n removeAllScheduledWorkouts(): Promise<boolean>;\n\n // Utility\n getSupportedActivityTypes(): ActivityType[];\n getSupportedGoalTypes(): string[];\n getSupportedLocationTypes(): LocationType[];\n}\n\nexport default requireNativeModule<ReactNativeWorkoutsModule>(\n \"ReactNativeWorkouts\",\n);\n"]}
1
+ {"version":3,"file":"ReactNativeWorkoutsModule.js","sourceRoot":"","sources":["../src/ReactNativeWorkoutsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAwJzD,eAAe,mBAAmB,CAChC,qBAAqB,CACtB,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport type {\n ActivityType,\n AuthorizationStatus,\n CustomWorkoutConfig,\n DateComponents,\n LocationType,\n PacerWorkoutConfig,\n ReactNativeWorkoutsModuleEvents,\n ScheduledWorkout,\n ScheduleResult,\n SingleGoalWorkoutConfig,\n SwimBikeRunWorkoutConfig,\n WorkoutPlan,\n WorkoutValidationResult,\n} from \"./ReactNativeWorkouts.types\";\n\ndeclare class ReactNativeWorkoutsModule\n extends NativeModule<ReactNativeWorkoutsModuleEvents> {\n // Constants\n /**\n * Whether Health data is available on this device.\n * On iOS simulators this is typically `false`.\n */\n readonly isAvailable: boolean;\n\n // Authorization\n /**\n * Returns the current WorkoutKit authorization status.\n */\n getAuthorizationStatus(): Promise<AuthorizationStatus>;\n /**\n * Prompts the user for WorkoutKit authorization (if needed).\n */\n requestAuthorization(): Promise<AuthorizationStatus>;\n\n // Validation\n /**\n * Returns whether a given goal type is supported for the provided activity + location.\n */\n supportsGoal(\n activityType: ActivityType,\n locationType: LocationType,\n goalType: string,\n ): Promise<boolean>;\n\n // Custom Workouts\n /**\n * Validates a custom workout config. (Legacy name: this does not persist anything.)\n */\n createCustomWorkout(\n config: CustomWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a custom workout via Apple's system modal.\n */\n previewWorkout(config: CustomWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a custom workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `useCustomWorkout(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n scheduleWorkout(\n config: CustomWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Single Goal Workouts\n /**\n * Validates a single-goal workout config. (Legacy name: this does not persist anything.)\n */\n createSingleGoalWorkout(\n config: SingleGoalWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a single-goal workout via Apple's system modal.\n */\n previewSingleGoalWorkout(config: SingleGoalWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a single-goal workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `useSingleGoalWorkout(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n scheduleSingleGoalWorkout(\n config: SingleGoalWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Pacer Workouts\n /**\n * Validates a pacer workout config. (Legacy name: this does not persist anything.)\n */\n createPacerWorkout(\n config: PacerWorkoutConfig,\n ): Promise<WorkoutValidationResult>;\n /**\n * Previews a pacer workout via Apple's system modal.\n */\n previewPacerWorkout(config: PacerWorkoutConfig): Promise<boolean>;\n /**\n * Schedules a pacer workout (syncs it to the Apple Watch Workout app).\n *\n * Prefer using `usePacerWorkout(...)` + `plan.scheduleAndSync(...)` for new code.\n */\n schedulePacerWorkout(\n config: PacerWorkoutConfig,\n date: DateComponents,\n ): Promise<ScheduleResult>;\n\n // Shared WorkoutPlan factories (object-oriented API)\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createCustomWorkoutPlan(config: CustomWorkoutConfig): Promise<WorkoutPlan>;\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createSingleGoalWorkoutPlan(\n config: SingleGoalWorkoutConfig,\n ): Promise<WorkoutPlan>;\n /**\n * Creates a stateful `WorkoutPlan` shared object (recommended API for new code).\n */\n createPacerWorkoutPlan(config: PacerWorkoutConfig): Promise<WorkoutPlan>;\n /**\n * Creates a stateful multisport `WorkoutPlan` shared object (recommended API for new code).\n */\n createSwimBikeRunWorkoutPlan(\n config: SwimBikeRunWorkoutConfig,\n ): Promise<WorkoutPlan>;\n\n // Scheduled Workouts Management\n /**\n * Lists scheduled workouts created by this app.\n */\n getScheduledWorkouts(): Promise<ScheduledWorkout[]>;\n /**\n * Removes a scheduled workout by ID.\n */\n removeScheduledWorkout(id: string): Promise<boolean>;\n /**\n * Removes all scheduled workouts created by this app.\n */\n removeAllScheduledWorkouts(): Promise<boolean>;\n\n // Utility\n getSupportedActivityTypes(): ActivityType[];\n getSupportedGoalTypes(): string[];\n getSupportedLocationTypes(): LocationType[];\n}\n\nexport default requireNativeModule<ReactNativeWorkoutsModule>(\n \"ReactNativeWorkouts\",\n);\n"]}
@@ -11,7 +11,10 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.homepage = package['homepage']
13
13
  s.platforms = {
14
- :ios => '17.0'
14
+ # The module APIs require iOS 17+ at runtime (WorkoutKit), but we keep the
15
+ # deployment target lower so apps can still build with e.g. iOS 15 targets.
16
+ # Calls on iOS < 17 will throw a descriptive "Unavailable" error.
17
+ :ios => '15.1'
15
18
  }
16
19
  s.swift_version = '5.9'
17
20
  s.source = { git: 'https://github.com/Janjiran/react-native-workouts' }
@@ -19,7 +22,10 @@ Pod::Spec.new do |s|
19
22
 
20
23
  s.dependency 'ExpoModulesCore'
21
24
 
22
- s.frameworks = 'WorkoutKit', 'HealthKit'
25
+ # HealthKit exists on older iOS versions, keep it strongly linked.
26
+ s.frameworks = 'HealthKit'
27
+ # WorkoutKit does NOT exist on iOS < 17; weak-link it so the app can still load.
28
+ s.weak_frameworks = 'WorkoutKit'
23
29
 
24
30
  s.pod_target_xcconfig = {
25
31
  'DEFINES_MODULE' => 'YES',
@@ -6,14 +6,17 @@ import UIKit
6
6
 
7
7
  // MARK: - Shared Objects
8
8
 
9
- @available(iOS 17.0, *)
10
9
  public final class WorkoutPlanObject: SharedObject {
11
- fileprivate let plan: WorkoutPlan
10
+ // Keep the plan opaque so this object can be referenced in ModuleDefinition on iOS < 17.
11
+ // We only cast/use WorkoutKit types behind runtime availability checks.
12
+ fileprivate let planHandle: Any
13
+ fileprivate let planId: String
12
14
  fileprivate let kind: String
13
15
  fileprivate let sourceConfig: [String: Any]
14
16
 
15
- fileprivate init(plan: WorkoutPlan, kind: String, sourceConfig: [String: Any]) {
16
- self.plan = plan
17
+ fileprivate init(planHandle: Any, planId: String, kind: String, sourceConfig: [String: Any]) {
18
+ self.planHandle = planHandle
19
+ self.planId = planId
17
20
  self.kind = kind
18
21
  self.sourceConfig = sourceConfig
19
22
  super.init()
@@ -21,7 +24,7 @@ public final class WorkoutPlanObject: SharedObject {
21
24
 
22
25
  fileprivate func export() -> [String: Any] {
23
26
  return [
24
- "id": plan.id.uuidString,
27
+ "id": planId,
25
28
  "kind": kind,
26
29
  "config": sourceConfig
27
30
  ]
@@ -29,19 +32,32 @@ public final class WorkoutPlanObject: SharedObject {
29
32
 
30
33
  @MainActor
31
34
  fileprivate func preview() async throws {
35
+ guard #available(iOS 17.0, *) else {
36
+ throw Exception(name: "Unavailable", description: "WorkoutKit requires iOS 17+. This API is unavailable on the current OS version.")
37
+ }
38
+ let plan = try self.getWorkoutPlan()
32
39
  try await presentWorkoutPreview(plan)
33
40
  }
34
41
 
35
42
  fileprivate func schedule(at date: DateComponents) async throws -> [String: Any] {
36
- do {
37
- try await WorkoutScheduler.shared.schedule(plan, at: date)
38
- return [
39
- "success": true,
40
- "id": plan.id.uuidString
41
- ]
42
- } catch {
43
- throw Exception(name: "ScheduleError", description: error.localizedDescription)
43
+ guard #available(iOS 17.0, *) else {
44
+ throw Exception(name: "Unavailable", description: "WorkoutKit requires iOS 17+. This API is unavailable on the current OS version.")
44
45
  }
46
+
47
+ let plan = try self.getWorkoutPlan()
48
+ await WorkoutScheduler.shared.schedule(plan, at: date)
49
+ return [
50
+ "success": true,
51
+ "id": planId
52
+ ]
53
+ }
54
+
55
+ @available(iOS 17.0, *)
56
+ private func getWorkoutPlan() throws -> WorkoutPlan {
57
+ guard let plan = planHandle as? WorkoutPlan else {
58
+ throw Exception(name: "InvalidState", description: "Workout plan handle is invalid")
59
+ }
60
+ return plan
45
61
  }
46
62
  }
47
63
 
@@ -61,10 +77,12 @@ public class ReactNativeWorkoutsModule: Module {
61
77
 
62
78
  Events("onAuthorizationChange")
63
79
 
80
+ let workoutKitUnavailableMessage = "WorkoutKit requires iOS 17+. This API is unavailable on the current OS version."
81
+
64
82
  // MARK: - Shared Object API (WorkoutPlan)
65
83
  Class("WorkoutPlan", WorkoutPlanObject.self) {
66
84
  Property("id") { planObject in
67
- return planObject.plan.id.uuidString
85
+ return planObject.planId
68
86
  }
69
87
 
70
88
  Property("kind") { planObject in
@@ -87,15 +105,60 @@ public class ReactNativeWorkoutsModule: Module {
87
105
  }
88
106
  }
89
107
 
90
- // Factories (return a WorkoutPlanObject handle to JS)
108
+ // MARK: - Authorization
109
+
110
+ AsyncFunction("getAuthorizationStatus") { () async throws -> String in
111
+ guard #available(iOS 17.0, *) else {
112
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
113
+ }
114
+
115
+ let status = await WorkoutScheduler.shared.authorizationState
116
+ return self.authorizationStateToString(status)
117
+ }
118
+
119
+ AsyncFunction("requestAuthorization") { () async throws -> String in
120
+ guard #available(iOS 17.0, *) else {
121
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
122
+ }
123
+
124
+ let status = await WorkoutScheduler.shared.requestAuthorization()
125
+ return self.authorizationStateToString(status)
126
+ }
127
+
128
+ // MARK: - Workout Validation
129
+
130
+ AsyncFunction("supportsGoal") { (activityType: String, locationType: String, goalType: String) throws -> Bool in
131
+ guard #available(iOS 17.0, *) else {
132
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
133
+ }
134
+
135
+ guard let activity = self.parseActivityType(activityType),
136
+ let location = self.parseLocationType(locationType),
137
+ let goal = self.parseGoalTypeForValidation(goalType) else {
138
+ return false
139
+ }
140
+
141
+ return CustomWorkout.supportsGoal(goal, activity: activity, location: location)
142
+ }
143
+
144
+ // MARK: - Plan factories (return a WorkoutPlanObject handle to JS)
145
+
91
146
  AsyncFunction("createCustomWorkoutPlan") { (config: [String: Any]) throws -> WorkoutPlanObject in
147
+ guard #available(iOS 17.0, *) else {
148
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
149
+ }
150
+
92
151
  let workout = try self.buildCustomWorkout(from: config)
93
152
  try self.validateCustomWorkout(workout)
94
153
  let plan = WorkoutPlan(.custom(workout))
95
- return WorkoutPlanObject(plan: plan, kind: "custom", sourceConfig: config)
154
+ return WorkoutPlanObject(planHandle: plan, planId: plan.id.uuidString, kind: "custom", sourceConfig: config)
96
155
  }
97
156
 
98
157
  AsyncFunction("createSingleGoalWorkoutPlan") { (config: [String: Any]) throws -> WorkoutPlanObject in
158
+ guard #available(iOS 17.0, *) else {
159
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
160
+ }
161
+
99
162
  guard let activityTypeStr = config["activityType"] as? String,
100
163
  let activity = self.parseActivityType(activityTypeStr),
101
164
  let goalConfig = config["goal"] as? [String: Any] else {
@@ -111,10 +174,14 @@ public class ReactNativeWorkoutsModule: Module {
111
174
 
112
175
  let workout = SingleGoalWorkout(activity: activity, location: location, goal: goal)
113
176
  let plan = WorkoutPlan(.goal(workout))
114
- return WorkoutPlanObject(plan: plan, kind: "singleGoal", sourceConfig: config)
177
+ return WorkoutPlanObject(planHandle: plan, planId: plan.id.uuidString, kind: "singleGoal", sourceConfig: config)
115
178
  }
116
179
 
117
180
  AsyncFunction("createPacerWorkoutPlan") { (config: [String: Any]) throws -> WorkoutPlanObject in
181
+ guard #available(iOS 17.0, *) else {
182
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
183
+ }
184
+
118
185
  guard let activityTypeStr = config["activityType"] as? String,
119
186
  let activity = self.parseActivityType(activityTypeStr),
120
187
  let targetConfig = config["target"] as? [String: Any] else {
@@ -126,10 +193,14 @@ public class ReactNativeWorkoutsModule: Module {
126
193
 
127
194
  let workout = PacerWorkout(activity: activity, location: location, distance: distance, time: time)
128
195
  let plan = WorkoutPlan(.pacer(workout))
129
- return WorkoutPlanObject(plan: plan, kind: "pacer", sourceConfig: config)
196
+ return WorkoutPlanObject(planHandle: plan, planId: plan.id.uuidString, kind: "pacer", sourceConfig: config)
130
197
  }
131
198
 
132
199
  AsyncFunction("createSwimBikeRunWorkoutPlan") { (config: [String: Any]) throws -> WorkoutPlanObject in
200
+ guard #available(iOS 17.0, *) else {
201
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
202
+ }
203
+
133
204
  guard let activitiesConfig = config["activities"] as? [[String: Any]] else {
134
205
  throw Exception(name: "InvalidConfig", description: "Missing required field: activities")
135
206
  }
@@ -143,52 +214,32 @@ public class ReactNativeWorkoutsModule: Module {
143
214
 
144
215
  let workout = SwimBikeRunWorkout(activities: activities, displayName: displayName)
145
216
  let plan = WorkoutPlan(.swimBikeRun(workout))
146
- return WorkoutPlanObject(plan: plan, kind: "swimBikeRun", sourceConfig: config)
147
- }
148
-
149
- // MARK: - Authorization
150
-
151
- AsyncFunction("getAuthorizationStatus") { () async -> String in
152
- let status = await WorkoutScheduler.shared.authorizationState
153
- return self.authorizationStateToString(status)
154
- }
155
-
156
- AsyncFunction("requestAuthorization") { () async -> String in
157
- let status = await WorkoutScheduler.shared.requestAuthorization()
158
- return self.authorizationStateToString(status)
159
- }
160
-
161
- // MARK: - Workout Validation
162
-
163
- AsyncFunction("supportsGoal") { (activityType: String, locationType: String, goalType: String) -> Bool in
164
- guard let activity = self.parseActivityType(activityType),
165
- let location = self.parseLocationType(locationType),
166
- let goal = self.parseGoalTypeForValidation(goalType) else {
167
- return false
168
- }
169
-
170
- return CustomWorkout.supportsGoal(goal, activity: activity, location: location)
217
+ return WorkoutPlanObject(planHandle: plan, planId: plan.id.uuidString, kind: "swimBikeRun", sourceConfig: config)
171
218
  }
172
219
 
173
220
  // MARK: - Custom Workout Creation
174
221
 
175
222
  AsyncFunction("createCustomWorkout") { (config: [String: Any]) throws -> [String: Any] in
223
+ guard #available(iOS 17.0, *) else {
224
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
225
+ }
226
+
176
227
  let workout = try self.buildCustomWorkout(from: config)
177
228
  try self.validateCustomWorkout(workout)
178
229
 
179
- do {
180
- return [
181
- "valid": true,
182
- "displayName": workout.displayName ?? (config["displayName"] as? String ?? "")
183
- ]
184
- } catch {
185
- throw Exception(name: "ValidationError", description: error.localizedDescription)
186
- }
230
+ return [
231
+ "valid": true,
232
+ "displayName": workout.displayName ?? (config["displayName"] as? String ?? "")
233
+ ]
187
234
  }
188
235
 
189
236
  // MARK: - Workout Preview (system modal)
190
237
 
191
238
  AsyncFunction("previewWorkout") { (config: [String: Any]) async throws -> Bool in
239
+ guard #available(iOS 17.0, *) else {
240
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
241
+ }
242
+
192
243
  let workout = try self.buildCustomWorkout(from: config)
193
244
  try self.validateCustomWorkout(workout)
194
245
  let plan = WorkoutPlan(.custom(workout))
@@ -198,6 +249,10 @@ public class ReactNativeWorkoutsModule: Module {
198
249
  }
199
250
 
200
251
  AsyncFunction("previewSingleGoalWorkout") { (config: [String: Any]) async throws -> Bool in
252
+ guard #available(iOS 17.0, *) else {
253
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
254
+ }
255
+
201
256
  guard let activityTypeStr = config["activityType"] as? String,
202
257
  let activity = self.parseActivityType(activityTypeStr),
203
258
  let goalConfig = config["goal"] as? [String: Any] else {
@@ -219,6 +274,10 @@ public class ReactNativeWorkoutsModule: Module {
219
274
  }
220
275
 
221
276
  AsyncFunction("previewPacerWorkout") { (config: [String: Any]) async throws -> Bool in
277
+ guard #available(iOS 17.0, *) else {
278
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
279
+ }
280
+
222
281
  guard let activityTypeStr = config["activityType"] as? String,
223
282
  let activity = self.parseActivityType(activityTypeStr),
224
283
  let targetConfig = config["target"] as? [String: Any] else {
@@ -238,35 +297,42 @@ public class ReactNativeWorkoutsModule: Module {
238
297
  // MARK: - Scheduled Workouts
239
298
 
240
299
  AsyncFunction("scheduleWorkout") { (config: [String: Any], date: [String: Any]) async throws -> [String: Any] in
300
+ guard #available(iOS 17.0, *) else {
301
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
302
+ }
303
+
241
304
  let workout = try self.buildCustomWorkout(from: config)
242
305
  try self.validateCustomWorkout(workout)
243
306
 
244
307
  let plan = WorkoutPlan(.custom(workout))
245
-
246
308
  let dateComponents = self.parseDateComponents(from: date)
247
309
 
248
- do {
249
- try await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
250
- return [
251
- "success": true,
252
- "id": plan.id.uuidString
253
- ]
254
- } catch {
255
- throw Exception(name: "ScheduleError", description: error.localizedDescription)
256
- }
310
+ await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
311
+ return [
312
+ "success": true,
313
+ "id": plan.id.uuidString
314
+ ]
257
315
  }
258
316
 
259
317
  AsyncFunction("getScheduledWorkouts") { () async throws -> [[String: Any]] in
318
+ guard #available(iOS 17.0, *) else {
319
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
320
+ }
321
+
260
322
  let workouts = await WorkoutScheduler.shared.scheduledWorkouts
261
- return workouts.map { plan in
323
+ return workouts.map { scheduled in
262
324
  return [
263
- "id": plan.plan.id.uuidString,
264
- "date": self.dateComponentsToDict(plan.date)
325
+ "id": scheduled.plan.id.uuidString,
326
+ "date": self.dateComponentsToDict(scheduled.date)
265
327
  ]
266
328
  }
267
329
  }
268
330
 
269
331
  AsyncFunction("removeScheduledWorkout") { (id: String) async throws -> Bool in
332
+ guard #available(iOS 17.0, *) else {
333
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
334
+ }
335
+
270
336
  guard let uuid = UUID(uuidString: id) else {
271
337
  throw Exception(name: "InvalidID", description: "Invalid workout ID format")
272
338
  }
@@ -276,27 +342,26 @@ public class ReactNativeWorkoutsModule: Module {
276
342
  throw Exception(name: "NotFound", description: "Workout not found")
277
343
  }
278
344
 
279
- do {
280
- try await WorkoutScheduler.shared.remove(workout.plan, at: workout.date)
281
- return true
282
- } catch {
283
- throw Exception(name: "RemoveError", description: error.localizedDescription)
284
- }
345
+ await WorkoutScheduler.shared.remove(workout.plan, at: workout.date)
346
+ return true
285
347
  }
286
348
 
287
349
  AsyncFunction("removeAllScheduledWorkouts") { () async throws -> Bool in
288
- do {
289
- try await WorkoutScheduler.shared.removeAllWorkouts()
290
- } catch {
291
- throw Exception(name: "RemoveError", description: error.localizedDescription)
350
+ guard #available(iOS 17.0, *) else {
351
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
292
352
  }
293
353
 
354
+ await WorkoutScheduler.shared.removeAllWorkouts()
294
355
  return true
295
356
  }
296
357
 
297
358
  // MARK: - Single Goal Workout
298
359
 
299
360
  AsyncFunction("createSingleGoalWorkout") { (config: [String: Any]) throws -> [String: Any] in
361
+ guard #available(iOS 17.0, *) else {
362
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
363
+ }
364
+
300
365
  guard let activityTypeStr = config["activityType"] as? String,
301
366
  let activity = self.parseActivityType(activityTypeStr),
302
367
  let goalConfig = config["goal"] as? [String: Any] else {
@@ -318,6 +383,10 @@ public class ReactNativeWorkoutsModule: Module {
318
383
  }
319
384
 
320
385
  AsyncFunction("scheduleSingleGoalWorkout") { (config: [String: Any], date: [String: Any]) async throws -> [String: Any] in
386
+ guard #available(iOS 17.0, *) else {
387
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
388
+ }
389
+
321
390
  guard let activityTypeStr = config["activityType"] as? String,
322
391
  let activity = self.parseActivityType(activityTypeStr),
323
392
  let goalConfig = config["goal"] as? [String: Any] else {
@@ -326,7 +395,6 @@ public class ReactNativeWorkoutsModule: Module {
326
395
 
327
396
  let goal = try self.parseWorkoutGoal(from: goalConfig)
328
397
  let location = self.parseLocationType(config["locationType"] as? String ?? "outdoor") ?? .outdoor
329
- _ = config["displayName"] as? String
330
398
 
331
399
  guard SingleGoalWorkout.supportsGoal(goal, activity: activity, location: location) else {
332
400
  throw Exception(name: "ValidationError", description: "Single goal workout not supported for this activity/location/goal")
@@ -336,21 +404,20 @@ public class ReactNativeWorkoutsModule: Module {
336
404
  let plan = WorkoutPlan(.goal(workout))
337
405
 
338
406
  let dateComponents = self.parseDateComponents(from: date)
339
-
340
- do {
341
- try await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
342
- return [
343
- "success": true,
344
- "id": plan.id.uuidString
345
- ]
346
- } catch {
347
- throw Exception(name: "ScheduleError", description: error.localizedDescription)
348
- }
407
+ await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
408
+ return [
409
+ "success": true,
410
+ "id": plan.id.uuidString
411
+ ]
349
412
  }
350
413
 
351
414
  // MARK: - Pacer Workout
352
415
 
353
416
  AsyncFunction("createPacerWorkout") { (config: [String: Any]) throws -> [String: Any] in
417
+ guard #available(iOS 17.0, *) else {
418
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
419
+ }
420
+
354
421
  guard let activityTypeStr = config["activityType"] as? String,
355
422
  let activity = self.parseActivityType(activityTypeStr),
356
423
  let targetConfig = config["target"] as? [String: Any] else {
@@ -370,6 +437,10 @@ public class ReactNativeWorkoutsModule: Module {
370
437
  }
371
438
 
372
439
  AsyncFunction("schedulePacerWorkout") { (config: [String: Any], date: [String: Any]) async throws -> [String: Any] in
440
+ guard #available(iOS 17.0, *) else {
441
+ throw Exception(name: "Unavailable", description: workoutKitUnavailableMessage)
442
+ }
443
+
373
444
  guard let activityTypeStr = config["activityType"] as? String,
374
445
  let activity = self.parseActivityType(activityTypeStr),
375
446
  let targetConfig = config["target"] as? [String: Any] else {
@@ -377,23 +448,17 @@ public class ReactNativeWorkoutsModule: Module {
377
448
  }
378
449
 
379
450
  let location = self.parseLocationType(config["locationType"] as? String ?? "outdoor") ?? .outdoor
380
- _ = config["displayName"] as? String
381
451
 
382
452
  let (distance, time) = try self.parsePacerDistanceAndTime(from: targetConfig)
383
453
  let workout = PacerWorkout(activity: activity, location: location, distance: distance, time: time)
384
454
  let plan = WorkoutPlan(.pacer(workout))
385
455
 
386
456
  let dateComponents = self.parseDateComponents(from: date)
387
-
388
- do {
389
- try await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
390
- return [
391
- "success": true,
392
- "id": plan.id.uuidString
393
- ]
394
- } catch {
395
- throw Exception(name: "ScheduleError", description: error.localizedDescription)
396
- }
457
+ await WorkoutScheduler.shared.schedule(plan, at: dateComponents)
458
+ return [
459
+ "success": true,
460
+ "id": plan.id.uuidString
461
+ ]
397
462
  }
398
463
 
399
464
  // MARK: - Activity Types
@@ -443,6 +508,7 @@ public class ReactNativeWorkoutsModule: Module {
443
508
 
444
509
  // MARK: - Helper Methods
445
510
 
511
+ @available(iOS 17.0, *)
446
512
  private func authorizationStateToString(_ state: WorkoutScheduler.AuthorizationState) -> String {
447
513
  switch state {
448
514
  case .authorized:
@@ -451,7 +517,7 @@ public class ReactNativeWorkoutsModule: Module {
451
517
  return "notDetermined"
452
518
  case .denied:
453
519
  return "denied"
454
- @unknown default:
520
+ default:
455
521
  return "unknown"
456
522
  }
457
523
  }
@@ -531,6 +597,7 @@ public class ReactNativeWorkoutsModule: Module {
531
597
  }
532
598
  }
533
599
 
600
+ @available(iOS 17.0, *)
534
601
  private func parseGoalTypeForValidation(_ type: String) -> WorkoutGoal? {
535
602
  switch type.lowercased() {
536
603
  case "open": return .open
@@ -541,6 +608,7 @@ public class ReactNativeWorkoutsModule: Module {
541
608
  }
542
609
  }
543
610
 
611
+ @available(iOS 17.0, *)
544
612
  private func parseWorkoutGoal(from config: [String: Any]) throws -> WorkoutGoal {
545
613
  guard let type = config["type"] as? String else {
546
614
  throw Exception(name: "InvalidGoal", description: "Goal type is required")
@@ -671,6 +739,7 @@ public class ReactNativeWorkoutsModule: Module {
671
739
  }
672
740
  }
673
741
 
742
+ @available(iOS 17.0, *)
674
743
  private func parseStepPurpose(_ purpose: String) -> IntervalStep.Purpose {
675
744
  switch purpose.lowercased() {
676
745
  case "work": return .work
@@ -679,7 +748,8 @@ public class ReactNativeWorkoutsModule: Module {
679
748
  }
680
749
  }
681
750
 
682
- private func parseWorkoutAlert(from config: [String: Any]) throws -> WorkoutAlert? {
751
+ @available(iOS 17.0, *)
752
+ private func parseWorkoutAlert(from config: [String: Any]) throws -> (any WorkoutAlert)? {
683
753
  guard let type = config["type"] as? String else {
684
754
  return nil
685
755
  }
@@ -730,7 +800,8 @@ public class ReactNativeWorkoutsModule: Module {
730
800
  return nil
731
801
  }
732
802
 
733
- private func paceRangeAlert(minMinutesPerUnit: Double, maxMinutesPerUnit: Double, unit: UnitLength) throws -> WorkoutAlert? {
803
+ @available(iOS 17.0, *)
804
+ private func paceRangeAlert(minMinutesPerUnit: Double, maxMinutesPerUnit: Double, unit: UnitLength) throws -> (any WorkoutAlert)? {
734
805
  guard minMinutesPerUnit > 0, maxMinutesPerUnit > 0 else {
735
806
  throw Exception(name: "InvalidAlert", description: "Pace min/max must be > 0")
736
807
  }
@@ -746,6 +817,7 @@ public class ReactNativeWorkoutsModule: Module {
746
817
  return SpeedRangeAlert.speed(lowSpeed...highSpeed, unit: .metersPerSecond)
747
818
  }
748
819
 
820
+ @available(iOS 17.0, *)
749
821
  private func validateCustomWorkout(_ workout: CustomWorkout) throws {
750
822
  let activity = workout.activity
751
823
  let location = workout.location
@@ -775,6 +847,7 @@ public class ReactNativeWorkoutsModule: Module {
775
847
  }
776
848
  }
777
849
 
850
+ @available(iOS 17.0, *)
778
851
  private func buildCustomWorkout(from config: [String: Any]) throws -> CustomWorkout {
779
852
  guard let activityTypeStr = config["activityType"] as? String,
780
853
  let activity = self.parseActivityType(activityTypeStr) else {
@@ -812,6 +885,7 @@ public class ReactNativeWorkoutsModule: Module {
812
885
  )
813
886
  }
814
887
 
888
+ @available(iOS 17.0, *)
815
889
  private func parseWorkoutStep(from config: [String: Any]) throws -> WorkoutStep {
816
890
  let goalConfig = config["goal"] as? [String: Any]
817
891
  let goal: WorkoutGoal
@@ -831,6 +905,7 @@ public class ReactNativeWorkoutsModule: Module {
831
905
  return step
832
906
  }
833
907
 
908
+ @available(iOS 17.0, *)
834
909
  private func parseIntervalStep(from config: [String: Any]) throws -> IntervalStep {
835
910
  let purposeStr = config["purpose"] as? String ?? "work"
836
911
  let purpose = self.parseStepPurpose(purposeStr)
@@ -848,6 +923,7 @@ public class ReactNativeWorkoutsModule: Module {
848
923
  return step
849
924
  }
850
925
 
926
+ @available(iOS 17.0, *)
851
927
  private func parseIntervalBlock(from config: [String: Any]) throws -> IntervalBlock {
852
928
  var block = IntervalBlock()
853
929
 
@@ -915,11 +991,8 @@ public class ReactNativeWorkoutsModule: Module {
915
991
  // MARK: - Workout Preview presentation helpers
916
992
 
917
993
  @MainActor
994
+ @available(iOS 17.0, *)
918
995
  fileprivate func presentWorkoutPreview(_ plan: WorkoutPlan) async throws {
919
- guard #available(iOS 17.0, *) else {
920
- throw Exception(name: "Unavailable", description: "Workout preview requires iOS 17+")
921
- }
922
-
923
996
  guard let viewController = topMostViewController() else {
924
997
  throw Exception(name: "NoViewController", description: "Unable to find a view controller to present from")
925
998
  }
@@ -932,9 +1005,27 @@ fileprivate func presentWorkoutPreview(_ plan: WorkoutPlan) async throws {
932
1005
  }
933
1006
 
934
1007
  fileprivate func topMostViewController() -> UIViewController? {
935
- let scenes = UIApplication.shared.connectedScenes
936
- let windowScene = scenes.first { $0.activationState == .foregroundActive } as? UIWindowScene
937
- let keyWindow = windowScene?.windows.first(where: { $0.isKeyWindow }) ?? windowScene?.windows.first
1008
+ // React Native / Expo apps can temporarily be in `.foregroundInactive` during transitions.
1009
+ // Also, `isKeyWindow` isn't always set early, so we fall back to a normal-level window.
1010
+ let windowScenes = UIApplication.shared.connectedScenes
1011
+ .compactMap { $0 as? UIWindowScene }
1012
+ .filter { scene in
1013
+ switch scene.activationState {
1014
+ case .foregroundActive, .foregroundInactive:
1015
+ return true
1016
+ default:
1017
+ return false
1018
+ }
1019
+ }
1020
+
1021
+ let candidateWindows = windowScenes.flatMap { $0.windows }
1022
+ let keyWindow =
1023
+ candidateWindows.first(where: { $0.isKeyWindow }) ??
1024
+ candidateWindows.first(where: { $0.windowLevel == .normal }) ??
1025
+ candidateWindows.first ??
1026
+ UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ??
1027
+ UIApplication.shared.windows.first
1028
+
938
1029
  guard let root = keyWindow?.rootViewController else { return nil }
939
1030
  return topMostViewController(from: root)
940
1031
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-workouts",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "React Native Expo module for Apple WorkoutKit - create, preview, and sync custom workouts to Apple Watch",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -46,9 +46,7 @@
46
46
  "engines": {
47
47
  "node": ">=18"
48
48
  },
49
- "dependencies": {
50
- "expo-build-properties": "~1.0.10"
51
- },
49
+ "dependencies": {},
52
50
  "devDependencies": {
53
51
  "@types/react": "~19.1.0",
54
52
  "expo": "^54.0.27",
@@ -7,11 +7,11 @@ import type {
7
7
  DateComponents,
8
8
  LocationType,
9
9
  PacerWorkoutConfig,
10
- SwimBikeRunWorkoutConfig,
11
10
  ReactNativeWorkoutsModuleEvents,
12
11
  ScheduledWorkout,
13
12
  ScheduleResult,
14
13
  SingleGoalWorkoutConfig,
14
+ SwimBikeRunWorkoutConfig,
15
15
  WorkoutPlan,
16
16
  WorkoutValidationResult,
17
17
  } from "./ReactNativeWorkouts.types";
@@ -59,7 +59,7 @@ declare class ReactNativeWorkoutsModule
59
59
  /**
60
60
  * Schedules a custom workout (syncs it to the Apple Watch Workout app).
61
61
  *
62
- * Prefer using `createCustomWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
62
+ * Prefer using `useCustomWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
63
63
  */
64
64
  scheduleWorkout(
65
65
  config: CustomWorkoutConfig,
@@ -80,7 +80,7 @@ declare class ReactNativeWorkoutsModule
80
80
  /**
81
81
  * Schedules a single-goal workout (syncs it to the Apple Watch Workout app).
82
82
  *
83
- * Prefer using `createSingleGoalWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
83
+ * Prefer using `useSingleGoalWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
84
84
  */
85
85
  scheduleSingleGoalWorkout(
86
86
  config: SingleGoalWorkoutConfig,
@@ -101,7 +101,7 @@ declare class ReactNativeWorkoutsModule
101
101
  /**
102
102
  * Schedules a pacer workout (syncs it to the Apple Watch Workout app).
103
103
  *
104
- * Prefer using `createPacerWorkoutPlan(...)` + `plan.scheduleAndSync(...)` for new code.
104
+ * Prefer using `usePacerWorkout(...)` + `plan.scheduleAndSync(...)` for new code.
105
105
  */
106
106
  schedulePacerWorkout(
107
107
  config: PacerWorkoutConfig,