How do AppEnum, AppEntity and EntityQuery work together, and how do parameter errors behave?

19.9.2 · L5 · Interview

Predict first

What do the query, defaults and an early error give?

import AppIntents
import Foundation

enum Priority: String, AppEnum {
    case low, high
    static let typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"
    static let caseDisplayRepresentations: [Priority: DisplayRepresentation] = [.low: "Low", .high: "High"]
}
struct ListEntity: AppEntity {
    static let typeDisplayRepresentation: TypeDisplayRepresentation = "List"
    static let defaultQuery = ListQuery()
    var id: String
    var name: String
    var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") }
}
struct ListQuery: EntityQuery {
    static let all = [ListEntity(id: "a", name: "Groceries"), ListEntity(id: "b", name: "Work")]
    func entities(for identifiers: [String]) async throws -> [ListEntity] { Self.all.filter { identifiers.contains($0.id) } }
    func suggestedEntities() async throws -> [ListEntity] { Self.all }
}
struct Open: AppIntent {
    static let title: LocalizedStringResource = "Open"
    @Parameter(title: "List") var list: ListEntity
    @Parameter(title: "Priority", default: .low) var priority: Priority
    @Parameter(title: "Count", default: 3) var count: Int
    func perform() async throws -> some IntentResult { .result() }
}
struct Greet: AppIntent {
    static let title: LocalizedStringResource = "Greet"
    @Parameter(title: "Name") var name: String
    func perform() async throws -> some IntentResult & ProvidesDialog {
        guard name.count > 2 else { throw $name.needsValueError("Name is too short") }
        return .result(dialog: "Hello")
    }
}
let q = ListQuery()
print(try await q.entities(for: ["b", "zzz"]).map(\.name))
print(try await q.suggestedEntities().map(\.id))
print(Priority.allCases.map(\.rawValue), Priority.caseDisplayRepresentations[.high]!.title.key)
let o = Open(); print("defaults:", o.priority, o.count)
var g = Greet(); g.name = "Al"
do { _ = try await g.perform() } catch { print("error type:", type(of: error)) }
print(Greet.openAppWhenRun, Greet.isDiscoverable, Greet.authenticationPolicy)
  • ["Work"] / ["a", "b"] / both cases and the key "High" / defaults: low 3 / error type: AppIntentError / false true alwaysAllowed
  • Both ids resolve
  • The error is a plain Swift Error type

Mental model. Entities are library cards: the system keeps the card number and asks the library (the query) for the book each time.

An AppEnum is a fixed set of choices with a display name per case; an AppEntity is a user-visible object with a stable id, a displayRepresentation and a defaultQuery. The query is how the system gets entities back: entities(for:) resolves saved identifiers (a Shortcut or a configured widget stores ids, not objects), suggestedEntities() fills pickers, and string or property queries add search. The check shows entities(for: ["b","zzz"]) returning only Work: unknown ids are simply dropped, which is how a deleted list disappears from a saved shortcut, so treat ids as durable and handle the “entity gone” case in perform(). @Parameter(default:) values apply without being set (low, 3).

Errors: throw $name.needsValueError("…") asks the system to re-prompt for that parameter; called directly, as in a test, it surfaces as an AppIntentError you can catch. Defaults worth remembering from the check: openAppWhenRun is false, isDiscoverable is true and authenticationPolicy is alwaysAllowed, so mark sensitive intents explicitly. Keep queries fast (they run in the picker UI) and index entities for Spotlight separately (Q408). Not run: the system UI that presents suggestions and re-prompts.

Unknown ids are dropped, defaults apply and needsValueError becomes an AppIntentError when called directly.

Common traps

  • Storing the whole object instead of the id. Stale data; the query is the source of truth.
  • Slow suggestedEntities(). The picker waits on it.

They might follow up with

  • How do I search entities? Conform the query to EntityStringQuery or EntityPropertyQuery.

Related questions

  • 19.5.1 What are App Intents, App Entities and App Shortcuts, how do they replace SiriKit and Intents extensions, and…
  • 19.4.2 How do configurable widgets, interactive buttons and Control Center controls work with App Intents?
  • 19.9.1 How do App Intents get their dependencies, and why does @Dependency crash in a unit test?

← How do App Intents get their dependencies, and why does @Dependency…   How does the system find your intents, and why can they not be… →