WidgetKit + AppIntent widget never sees shared snapshots (Flutter host app)

Environment

  • iOS 17.2, Xcode 16.2, physical iPhone (12 Pro)
  • Main app in Flutter
  • WidgetKit extension written in Swift (Swift‑PM package)
  • Shared App Group: group.cool.glance.shared
  • Widget uses an AppIntent (FeedSelectionIntent) + custom entity (FeedAppEntity)
  • Flutter bridge writes JSON snapshots for the widget

Observed behaviour

  • Flutter prints the snapshot payload and writes /…/AppGroup/<uuid>/Library/Caches/feed_snapshots.json.
  • Widget gallery only shows the plain grey system placeholder (my sample placeholder never appears).
  • Console log every time WidgetKit runs:
chronod: Unable to resolve default intent (appintent:FeedSelectionIntent) for extension cool.glance.app.widget
Error Domain=LNMetadataProviderErrorDomain Code=9000
LinkMetadata.BundleMetadataExtractionError.aggregateMetadataIsEmpty
  • Added os_log in the widget + bridge (WidgetsBridgePlugin, FeedSnapshotStore, FeedEntityQuery, FeedSummaryTimeline), but none of them ever appear. That suggests the widget bundle can’t see the compiled AppIntent metadata or the snapshot file even though it’s there.

Code (trimmed to essentials)

FeedSelectionIntent.swift

struct FeedSelectionIntent: AppIntent, WidgetConfigurationIntent {
  static var title: LocalizedStringResource = "Feed"
  static var description = IntentDescription("Choose which feed should appear in the widget.")

  @Parameter(title: "Feed",
             requestValueDialog: IntentDialog("Select which feed to display."))
  var feed: FeedAppEntity?

  static var parameterSummary: some ParameterSummary { Summary("Show \(\.$feed)") }

  init() { feed = FeedAppEntity.sample }
  init(feed: FeedAppEntity?) { self.feed = feed }

  static var defaultValue: FeedSelectionIntent { FeedSelectionIntent(feed: .sample) }

  func perform() async throws -> some IntentResult { .result() }
}

FeedSnapshotStore.loadSnapshots()

guard let containerURL = fileManager.containerURL(
        forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
  os_log("FeedSnapshotStore: missing app group container %{public}s", log: Self.log, type: .error, appGroupIdentifier)
  return []
}

let fileURL = SharedConstants.feedSnapshotRelativePath.reduce(containerURL) { url, component in
  url.appendingPathComponent(component, isDirectory: component != SharedConstants.feedSnapshotFileName)
}

guard let data = try? Data(contentsOf: fileURL), !data.isEmpty else {
  os_log("FeedSnapshotStore: no snapshot data found at %{public}s", log: Self.log, type: .info, fileURL.path)
  return []
}

// decode FeedSnapshotEnvelope…

WidgetsBridgePlugin.writeSnapshots (Flutter → widget)

guard let containerURL = fileManager.containerURL(
        forSecurityApplicationGroupIdentifier: SharedConstants.appGroupIdentifier) else {
  result(FlutterError(code: "container-unavailable", message: "Unable to locate shared app group container.", details: nil))
  return
}

let targetDir = SharedConstants.feedSnapshotRelativePath.dropLast().reduce(containerURL) {
  $0.appendingPathComponent($1, isDirectory: true)
}
try fileManager.createDirectory(at: targetDir, withIntermediateDirectories: true)

let targetURL = targetDir.appendingPathComponent(SharedConstants.feedSnapshotFileName, isDirectory: false)
try data.write(to: targetURL, options: .atomic)

WidgetCenter.shared.reloadTimelines(ofKind: "GlanceSummaryWidget")
os_log("WidgetsBridgePlugin: wrote snapshots for %{public}d feeds at %{public}s",
       log: WidgetsBridgePlugin.log,
       type: .info,
       envelope.feeds.count,
       targetURL.path)

Info.plist for the widget contains only:

<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
<key>NSExtensionAttributes</key>
<dict>
  <key>WKAppBundleIdentifier</key>
  <string>cool.glance.app</string>
</dict>

(If I add NSExtensionPrincipalClass, the install fails with “principal class not allowed for com.apple.widgetkit-extension”, so it stays out.)

What I’ve double‑checked

  • App Group entitlement present on Runner.app and the widget extension.
  • Snapshot file definitely exists under Library/Caches/feed_snapshots.json (size updates when Flutter writes).
  • Code matches Apple’s “Making a configurable widget” sample (custom WidgetConfigurationIntent, entity, and timeline provider).
  • Cleaned build folders (Flutter + Xcode), reinstalled app from scratch, but I still don’t see any of the os_log messages from the widget extension-only the LinkMetadata error above.
  • Placeholder entry (SampleSnapshots.recentSummary) is wired up; yet the system never uses it and always drops to the generic grey preview.

Questions

  1. Does LinkMetadata.BundleMetadataExtractionError.aggregateMetadataIsEmpty mean WidgetKit can’t see the compiled AppIntent metadata? If so, what could cause that when the extension is built via Swift Package Manager inside a Flutter project?
  2. Are there extra build settings or plist keys required so the AppIntent metadata gets embedded in the widget bundle?
  3. Any reason the widget would never reach my FeedSnapshotStore logs even though the file is written and the App Group is configured?

Any help connecting the dots would be hugely appreciated.

WidgetKit &#43; AppIntent widget never sees shared snapshots (Flutter host app)
 
 
Q