A widget extension runs in a separate process with tight time and memory limits. Giving it direct access to the application's entire data layer creates fragile coupling.
Publish the exact display state it needs:
struct HydrationWidgetSnapshot: Codable {
let day: Date
let updatedAt: Date
let waterMilliliters: Int?
let goalMilliliters: Int?
}
Store the encoded snapshot in an App Group container under a versioned key. After an app-side write, publish a new snapshot and request WidgetCenter to reload that widget kind.
The widget renders missing data deliberately: placeholders for unknown values, no progress ring when the goal is absent, and a stale timestamp if freshness matters.
Snapshot models decouple extension releases from SwiftData migrations and reduce startup work. They also create a clear privacy boundary-only fields intended for the widget leave the main store.
Keep timelines conservative. The app can trigger reloads after meaningful changes, while WidgetKit controls actual scheduling.
Test decoding old snapshot versions, missing App Group configuration, nil values, clamped progress, and day rollover. A widget is another client of the product, even when it ships inside the same Xcode project.