Skip to content
AB

Interactive Widgets with App Intents

Perform a small background action, refresh authoritative data, and republish the widget without opening the app.

Ashish BhakhandPublished

An interactive widget button is a write path, not merely UI. A “+250 ml” action must validate its input, update durable data, and refresh the displayed snapshot.

Code
struct AddWaterIntent: AppIntent {
    static var openAppWhenRun = false

    func perform() async throws -> some IntentResult {
        try await hydrationClient.add(milliliters: 250)
        let snapshot = try await hydrationClient.todaySnapshot()
        try widgetPublisher.publish(snapshot)
        return .result()
    }
}

Clamp externally supplied amounts even when the current button uses a constant. Intent parameters can gain other entry points later.

The write should be idempotent only if its identity supports that behavior; repeated taps to add water are usually separate intentional events. Disable duplicate in-flight UI where possible and let the data model distinguish events.

Do not guess the new total by adding to the old snapshot. Refetch authoritative local state after the write, then publish and request a timeline reload.

App and extension targets often share the intent type while the app target supplies concrete side-effect implementations. Keep bundle capabilities and App Group entitlements aligned.

Test the action without launching SwiftUI: validation, failed write, successful republish, and missing shared container. Small widget actions deserve the same correctness as in-app buttons.

← Back to all writing