A workout Live Activity is a projection of workout state. It should not become a second source of truth.
ActivityKit separates stable attributes from changing content:
struct WorkoutAttributes: ActivityAttributes {
let workoutID: String
let workoutName: String
struct ContentState: Codable, Hashable {
let startedAt: Date
let isPaused: Bool
let currentExercise: String?
let completedSets: Int
}
}
Map the persisted workout draft into this state whenever the session changes. Start an activity for active or paused sessions, update an existing match, and end it when completed or cancelled.
Reconcile on launch. If two activities exist for one workout, keep one and end extras. If the application supports only one active workout, end unrelated stale activities too.
Pause and resume App Intents must mutate the shared workout draft first, then update the Live Activity. The visible timer follows durable state rather than maintaining independent time.
Design every presentation: Lock Screen, expanded Dynamic Island, compact leading and trailing, and minimal. Missing exercise data needs a useful fallback.
Test state mapping and reconciliation as pure logic. Device testing still matters for entitlement, target membership, interaction, and dismissal behavior. Live Activities remain reliable when they mirror one durable session lifecycle.