Setting Up Home Widgets
What you'll learn
- Generating the Home Widget SDK with the required App Group ID
- Configuring Android manifest entries and resource files
- 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
Pro Feature
home_widget requires an active Pro subscription. Run archipelago auth status to confirm your tier.
iOS requires manual Xcode steps
The brick cannot modify .xcodeproj. Follow Step 4 carefully — skipping any Xcode step causes silent widget failures on iOS.
Step 1: Generate the Home Widget SDK
archipelago generate home_widgetYou will be prompted for:
- appGroupId — the App Group identifier registered in your Apple Developer account, e.g.
group.com.example.app(required for iOS shared storage)
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 # Abstract SDK contract
│ └── models/
│ ├── widget_data.dart # Data payload value object
│ └── widget_tap_event.dart # Tap event (action, payload, widgetId)
└── home_widget_impl/
└── lib/src/
├── home_widget_sdk_impl.dart # home_widget package wrapper
├── di/ # GetIt module
└── platform/
├── android/res/ # layout/ + xml/ resource files
└── ios/SampleWidget.swift # SwiftUI widget sourceStep 3: Configure Android
3a. Add the broadcast receiver to AndroidManifest.xml
Open android/app/src/main/AndroidManifest.xml and add inside <application>:
<receiver android:name=".SampleAppWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/sample_app_widget_info" />
</receiver>3b. Copy the resource files
Copy the generated resource directories into your host app:
cp -r features/home_widget/home_widget_impl/lib/src/platform/android/res/layout/ \
android/app/src/main/res/layout/
cp -r features/home_widget/home_widget_impl/lib/src/platform/android/res/xml/ \
android/app/src/main/res/xml/Edit res/xml/sample_app_widget_info.xml to set the correct minWidth, minHeight, and updatePeriodMillis for your widget size.
Step 4: Configure iOS (Xcode — manual 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 the generated platform directory 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. Set the App Group in Flutter
Call this before any widget read/write — typically in main.dart or your DI bootstrap:
await HomeWidget.setAppGroupId('group.com.example.app');Step 5: Register the Feature
In your shell app's bootstrap.dart:
import 'package:home_widget_impl/home_widget_impl.dart';
FeatureRegistry.register(HomeWidgetImpl());Step 6: Push Data to the Widget
await getIt<HomeWidgetSDK>().updateWidget(
const WidgetData(values: {'title': 'Hello from Flutter'}),
);On iOS, this writes to UserDefaults(suiteName: appGroupId) then calls WidgetCenter.reloadAllTimelines(), which triggers the SampleProvider in your Swift extension to re-render.
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 route or action string set in the widget
// event.payload — optional Map<String, dynamic>
// event.widgetId — the Android/iOS widget instance ID
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 rendering the first route.
Step 8: iOS Data Flow
Understanding the data path helps when debugging widget rendering issues:
Flutter updateWidget()
→ UserDefaults(suiteName: appGroupId) [shared storage]
→ WidgetCenter.reloadAllTimelines() [tells WidgetKit to refresh]
→ SampleProvider.getTimeline() [Swift — reads UserDefaults]
→ Widget re-renders with new valuesBoth the main app and the widget extension must have the same App Group — mismatched IDs are the most common source of widgets showing stale data.
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