Feature SDKs
How to add home screen widgets to a Flutter app
Pro Reference for home_widget: every variable, option and package it generates. Command: archipelago generate home_widget
Generate the Home Widget SDK with an App Group ID, see what is set up on Android, add the iOS Widget Extension in Xcode, and push data and handle taps.
What you'll learn
- Generating the Home Widget SDK with the required App Group ID
- What the brick sets up on Android for you
- Setting up an iOS Widget Extension in Xcode manually
- Pushing data from Flutter and handling widget tap events
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
- An App Group registered in the Apple Developer console for iOS widgets
A Pro subscription is required. Run archipelago auth status to confirm your tier.
iOS needs manual Xcode steps
The brick cannot change .xcodeproj. Follow Step 4 in full. A skipped Xcode step makes the widget fail with no error.
Step 1: Generate the Home Widget SDK
archipelago generate home_widgetThe prompt is:
- appGroupId: the App Group ID from your Apple Developer account, for example
group.com.example.app. It has no default.
Or use a config file:
{
"appGroupId": "group.com.example.app"
}archipelago generate home_widget --config widget_config.jsonStep 2: Understand the Generated Structure
features/home_widget/
├── home_widget_api/
│ └── lib/src/
│ ├── home_widget_sdk.dart # HomeWidgetSDK contract
│ └── models/ # WidgetData, WidgetTapEvent
└── home_widget_impl/
├── android/src/main/
│ ├── AndroidManifest.xml # declares the widget receiver
│ ├── kotlin/com/archipelago/home_widget_impl/SampleAppWidgetProvider.kt
│ └── res/ # layout/ and xml/ files
├── ios/Classes/HomeWidgetExtensionResources/
│ ├── Info.plist
│ └── SampleWidget.swift
└── lib/src/
├── home_widget_sdk_impl.dart
└── di/The post-generation hook adds both packages to the workspace and the app pubspec.yaml. It registers HomeWidgetImplPackageModule in lib/di/injector.dart. It also creates gradle/libs.versions.toml when your project has none.
Step 3: Check Android
You do not edit the app on Android. home_widget_impl is a Flutter plugin for Android: its AndroidManifest.xml declares SampleAppWidgetProvider, and Gradle merges that receiver and the package's res/ files into the app.
To size the widget, edit features/home_widget/home_widget_impl/android/src/main/res/xml/sample_app_widget_info.xml (minWidth, minHeight, updatePeriodMillis).
Step 4: Configure iOS (Manual Xcode Steps)
The brick cannot patch .xcodeproj, so these steps must be done by hand in Xcode.
4a. Add a Widget Extension target
- Open
ios/Runner.xcworkspacein Xcode - File > New > Target > Widget Extension
- Uncheck "Include Configuration Intent" (unless you need user-configurable widgets)
- Name it
SampleWidget
4b. Copy the generated Swift source
Copy SampleWidget.swift from features/home_widget/home_widget_impl/ios/Classes/HomeWidgetExtensionResources/ into the new Widget Extension target folder.
4c. Enable App Groups
- Select the Runner target > Signing & Capabilities > + Capability > App Groups
- Add
group.com.example.app(matchingappGroupIdfrom generation) - Repeat for the SampleWidget extension target, same group ID
4d. Add WidgetKit.framework
In the SampleWidget target > Build Phases > Link Binary With Libraries, add WidgetKit.framework.
4e. The App Group call
You write no Dart for the App Group. HomeWidgetSDKImpl calls HomeWidget.setAppGroupId once when it is built. Each updateWidget, getCurrentData and getInitialTapEvent call waits for it.
Step 5: Check the Registration
The hook registers the module. You write no registration code. HomeWidgetSDKImpl is an eager SDK that registers itself with FeatureSDKRegistry when DI builds it. It has no routes.
Check getIt<HomeWidgetSDK>().isSupported. It is true on Android and iOS and false on web and desktop.
Step 6: Push Data to the Widget
await getIt<HomeWidgetSDK>().updateWidget(
const WidgetData(values: {'title': 'Hello from Flutter'}),
);WidgetData values can be String, int, double or bool. Another type throws an ArgumentError, which updateWidget catches and logs. The call never throws to you.
On iOS, this writes to UserDefaults(suiteName: appGroupId) and calls WidgetCenter.reloadAllTimelines(). The SampleProvider in your Swift extension then re-renders.
On Android, it broadcasts an APPWIDGET_UPDATE intent to the SampleAppWidgetProvider.
Step 7: Handle Widget Tap Events
Listen for taps while the app is running
getIt<HomeWidgetSDK>().tapStream.listen((event) {
// event.action: the host part of the tap URI
// event.payload: the query parameters
// event.widgetId: the scheme part of the tap URI
router.push(event.action);
});Handle a tap that launched the app cold
final initialTap = await getIt<HomeWidgetSDK>().getInitialTapEvent();
if (initialTap != null) {
router.push(initialTap.action);
}Call getInitialTapEvent() once after DI is ready, before the first route renders.
getCurrentData() reads only the title key. Extend it to read every key your layout uses.
Step 8: iOS Data Flow
Understanding the data path helps when debugging widget rendering issues:
flowchart TD
a["Flutter updateWidget()"] --> b["UserDefaults(suiteName: appGroupId)<br/><i>shared storage</i>"]:::water
b --> c["WidgetCenter.reloadAllTimelines()<br/><i>tells WidgetKit to refresh</i>"]
c --> d["SampleProvider.getTimeline()<br/><i>Swift, reads UserDefaults</i>"]
d --> e["Widget re-renders with new values"]
The main app and the widget extension must use the same App Group. Mismatched IDs are the most common cause of stale widgets.
Common Customizations
| Customization | Where to Change |
|---|---|
| Multiple widget sizes | Add entries in res/xml/ (Android) and use supportedFamilies in Swift (iOS) |
| Configurable widget (user settings) | Check "Include Configuration Intent" when adding the target |
| Widget deep-link routes | Set event.action to an auto_route path in your Swift/Android source |
| Periodic background refresh | Set updatePeriodMillis in sample_app_widget_info.xml (Android) |
Next Steps
- Set up the Connectivity SDK to skip widget updates when offline
- Configure monitoring to track widget update failures