Skip to content

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

bash
archipelago generate home_widget

You 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:

json
{
  "appGroupId": "group.com.example.app"
}
bash
archipelago generate home_widget --config widget_config.json

Step 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 source

Step 3: Configure Android

3a. Add the broadcast receiver to AndroidManifest.xml

Open android/app/src/main/AndroidManifest.xml and add inside <application>:

xml
<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:

bash
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

  1. Open ios/Runner.xcworkspace in Xcode
  2. File > New > Target > Widget Extension
  3. Uncheck "Include Configuration Intent" (unless you need user-configurable widgets)
  4. 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

  1. Select the Runner target > Signing & Capabilities > + Capability > App Groups
  2. Add group.com.example.app (matching appGroupId from generation)
  3. 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:

dart
await HomeWidget.setAppGroupId('group.com.example.app');

Step 5: Register the Feature

In your shell app's bootstrap.dart:

dart
import 'package:home_widget_impl/home_widget_impl.dart';

FeatureRegistry.register(HomeWidgetImpl());

Step 6: Push Data to the Widget

dart
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

dart
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

dart
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 values

Both 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

CustomizationWhere to Change
Multiple widget sizesAdd entries in res/xml/ (Android) and use @supported​Families in Swift (iOS)
Configurable widget (user settings)Check "Include Configuration Intent" when adding the target
Widget deep-link routesSet event.action to an auto_route path in your Swift/Android source
Periodic background refreshSet updatePeriodMillis in sample_app_widget_info.xml (Android)

Next Steps

Built by Banua Coder