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_login 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_logmessages 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
- Does
LinkMetadata.BundleMetadataExtractionError.aggregateMetadataIsEmptymean 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? - Are there extra build settings or plist keys required so the AppIntent metadata gets embedded in the widget bundle?
- Any reason the widget would never reach my
FeedSnapshotStorelogs even though the file is written and the App Group is configured?
Any help connecting the dots would be hugely appreciated.