Skip to content
Back to archipelago.banuacoder.com

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 ​

bash
archipelago generate home_widget

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

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

Step 2: Understand the Generated Structure ​

text
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

  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 features/home_widget/home_widget_impl/ios/Classes/HomeWidgetExtensionResources/ 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. 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 ​

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

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

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

CustomizationWhere to Change
Multiple widget sizesAdd 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 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 ​