---
title: Configurable Widgets
description: Create configurable Widgets and connect them to your Flutter App
---

# Configurable Widgets

Sometimes you want to give your users the ability to configure their HomeScreenWidgets to their liking.
For example a weather widget that can show the weather for different locations or a todo list that can show different lists.

## iOS

On iOS there are two ways to make your App Configurable.
The new and recommended way by Apple is to use [WidgetConfigurationIntent](#widgetconfigurationintent)
you can also provide backwards compatibility using [SiriKit Intents](#sirikit-intents)

### WidgetConfigurationIntent

<Steps>
    <Step title="Create Widget Extension">
        If you create a new Widget add an extension by going <kbd>File</kbd> > <kbd>New</kbd> > <kbd>Target</kbd> > <kbd>Widget Extension</kbd>
        This time checking the box for <kbd>Include Configuration App Intent</kbd>
        ![Setting up the Extension with Configuration Intent](/assets/configurable/ios/create-widget-extension-ios-with-configuration.webp)

        The generated Widget code includes the following classes:
        - `AppIntentTimelineProvider` - Provides a Timeline of entries at which the System will update the Widget automatically using an AppIntent for configuration
        - `TimelineEntry` - Represents the Data Object used to build the Widget. The `date` field is necessary and defines the point in time at which the Timeline would update
        - `View` - The Widget itself, which is built with SwiftUI
        - `Widget` - Configuration: Make note of the `kind` you set in the Configuration as this is what's needed to update the Widget from Flutter.

        In a separate file there is a `WidgetConfigurationIntent` defined that will determine the configuration options of the Widget in `AppIntent.swift`.

    </Step>

    <Step title="Adjusting the Configuration Intent">

        You can add fields that appear in the configuration by changing the generated `ConfigurationAppIntent` class.

        ```swift
        @Parameter(title: "Name", default: "World")
        var name: String
        ```

        You can access the fields of the configuration when building the Widget UI

        ```swift
        struct ConfigurableWidgetEntryView : View {
            var entry: Provider.Entry

            var body: some View {
                VStack {
                    Text("Hello")
                    Text(entry.configuration.name)
                }
            }
        }
        ```

        <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/widget-with-name-configuration.webp"/>
            <Video src="/assets/configurable/ios/world-to-flutter.mp4"/>
        </div>

    </Step>

    <Step title="Getting Data from Flutter to the Configuration">

        #### App Groups
        In order for sending Data from your Flutter App to the Widget we need to use [App Groups](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.application-groups).
        <Info>App Groups require a paid Apple Developer Account</Info>

        ##### Create a new App Group
        Visit the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/applicationGroup/add/) and create a new App Group.
        
        ##### Enable App Groups in XCode
        Add the App Group capability to <b>both the App Target (Runner) and your Widget Extension</b> in XCode.
        ![Enable App Groups in XCode](/assets/configurable/ios/app-group.webp)

        ##### Register App Group in Flutter
        In your Flutter code register the App Group with `home_widget`

        ```dart
        import 'package:home_widget/home_widget.dart';
        void main() {
            WidgetFlutterBinding.ensureInitialized();
            HomeWidget.setAppGroupId('group.YOUR_APP_GROUP_ID');
            runApp(MyApp());
        }
        ```

        #### Send Data

        You can store data from Flutter for the Widget using `HomeWidget.saveWidgetData` to use it as options in the configuration panel.

        ```dart
        final punctuations = [
            '!',
            '!!!',
            '.',
            '?',
            // Wave Emoji
            '\u{1F44B}',
        ];
        await HomeWidget.saveWidgetData(
            'punctuations',
            jsonEncode(punctuations),
        );
        ```

        #### Use Values from Flutter as Options in Customize Panel

        In XCode and Swift add an `AppEntity` and matching `EntityQuery`


        ```swift

        @Parameter(title: "Punctuation")
        var punctuation: PunctuationEntity
        }

        // Make Entity Codable so home_widget
        // That way home_widget can best extract the values from a configuration
        struct PunctuationEntity: AppEntity, Codable {

        let id: String

        static var typeDisplayRepresentation: TypeDisplayRepresentation = "Punctuation"
        static var defaultQuery = PunctuationQuery()

        var displayRepresentation: DisplayRepresentation {
            DisplayRepresentation(title: "\(id)")
        }
        }

        struct PunctuationQuery: EntityQuery {

        func punctuations() -> [PunctuationEntity] {
            let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP")

            do {
            let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data(
                using: .utf8)!
            let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations)
            return stringArray.map { punctuation in
                PunctuationEntity(id: punctuation)

            }
            } catch {
            return [PunctuationEntity(id: "!")]
            }

        }

        func entities(for identifiers: [PunctuationEntity.ID]) async throws -> [PunctuationEntity] {
            let results = punctuations().filter { identifiers.contains($0.id) }
            return results
        }

        func suggestedEntities() async throws -> [PunctuationEntity] {
            return punctuations()
        }

        func defaultResult() async -> PunctuationEntity? {
            try? await suggestedEntities().first
        }

        }
        ```

        Now you can access this new parameter in your Widget Code

        ```swift
        struct ConfigurableWidgetEntryView: View {
        var entry: Provider.Entry

        var body: some View {
            VStack {
            Text("Hello")
            Text(entry.configuration.name)
            Text(entry.configuration.punctuation.id) // [!code highlight]
            }
        }
        }
        ```
    </Step>

    <Step title="Seeing Configuration in Flutter">

        You need to let home_widget know about the relation of the Intent and the `kind` (what you need for updating the widget).

        ##### Add `Runner` as a Target of the Widget

        Open the Intent Swift File and in the Details Pane enable the `Target Membership` for the `Runner` Target

        ![Enable Target Membership for Runner](/assets/configurable/ios/app-intent-target-membership.webp)

        ##### Register Configuration in AppDelegate

        In your `AppDelegate.swift` register the Configuration Intent together with the `kind` of your Widget

        ```swift
        import Flutter
        import UIKit
        import home_widget // [!code ++]

        @main
        @objc class AppDelegate: FlutterAppDelegate {
        override func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            GeneratedPluginRegistrant.register(with: self)
            if #available(iOS 17.0, *) { // [!code ++]
            HomeWidgetPlugin.setConfigurationLookup(to: [ // [!code ++]
                "ConfigurableWidget": ConfigurationAppIntent.self // [!code ++]
            ]) // [!code ++]
            } // [!code ++]
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        }

        ```

        To get the configuration a user made to a Widget you can use `HomeWidget.getInstalledWidgets()`.

        ```dart
        final configuration = await HomeWidget.getInstalledWidgets();
        ```

        this will give you a list of `HomeWidgetInfo` in there there is a `Map<String, dynamic>? configuration`.

        <Warning>
            On iOS Simulators `HomeWidget.getInstalledWidgets()` will always return an empty list.

            To fully test this you should test on a real iOS Device
        </Warning>

        For our Greeting Example the Configuration would be:

        ```json
        {
            "name": "Documentation",
            "punctuation": {
                "id": "!!!"
            }
        }
        ```

        <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/configured-widgets.webp"/>
            <Image src="/assets/configurable/ios/configuration-in-flutter.webp"/>
        </div>
    </Step>
</Steps>

### SiriKit Intents

Before iOS 17 SiriKit Intents where used to build configurable widgets. You can use this to add backwards compatibility.

<Info>
    You could also use SiriKit Intents standalone however it is unclear how long Apple will support this.
</Info>

#### Basic setup

<Steps>
    <Step title="Create Intent">
        Create a new SiriKit Intent File by going to <kbd>File</kbd> > <kbd>New</kbd> > <kbd>SiriKit Intent File</kbd>

        <Image src="/assets/configurable/ios/siri-intent-file.webp"/>

        Create a new Intent using the <kbd>+</kbd> button in the bottom left.

        In the Intent set <b>Category</b> to `View` and enable `Intent is eligible for widgets`

        <Image src="/assets/configurable/ios/intent-configuration.webp"/>

        Add a simple parameter to the Intent. FOr our example a `String` parameter called `name`

        <Image src="/assets/configurable/ios/simple-parameter.webp"/>
    </Step>

    <Step title="Using Intent in Widget">
    To use our custom generated Intent in the generated Widget we need to to adjust the automatically generated code to conform to the correct APIs.

    Adjust your `SimpleEntry` to use properties instead of the Intent so we can use both the AppIntent and the SiriIntent.
    ```swift
    struct SimpleEntry: TimelineEntry {
        let date: Date
        let configuration: ConfigurationAppIntent // [!code --]
        let name: String // [!code ++]
        var punctuation: String? = nil // [!code ++]
    }    
    ```

    Create an `IntentTimelineProvider`

    ```swift
    struct IntentProvider: IntentTimelineProvider {
        typealias Entry = SimpleEntry

        typealias Intent = GreetingIntentIntent

        func placeholder(in context: Context) -> SimpleEntry {
            SimpleEntry(date: Date(), name: "World")
        }

        func getSnapshot(
            for configuration: GreetingIntentIntent, in context: Context,
            completion: @escaping (SimpleEntry) -> Void
        ) {
            completion(SimpleEntry(date: Date(), name: configuration.Name))
        }

        func getTimeline(
            for configuration: GreetingIntentIntent, in context: Context,
            completion: @escaping (Timeline<SimpleEntry>) -> Void
        ) {
            var entries: [SimpleEntry] = []

            // Generate a timeline consisting of five entries an hour apart, starting from the current date.
            let currentDate = Date()
            for hourOffset in 0..<5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, name: configuration.Name)
            entries.append(entry)
            }

            completion(Timeline(entries: entries, policy: .atEnd))
        }
    }
    ```

    In the Widget definition add code to use the appropriate configuration provider.

    ```swift
    var body: some WidgetConfiguration {
        if #available(iOS 17.0, *) { // [!code ++]
        AppIntentConfiguration( // [!code --]
            return AppIntentConfiguration( // [!code ++]
                kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()
            ) {
                entry in
                ConfigurableWidgetEntryView(entry: entry)
                .containerBackground(.fill.tertiary, for: .widget)
            }
        } else { // [!code ++]
            return IntentConfiguration( // [!code ++]
                kind: kind, // [!code ++]
                intent: GreetingIntentIntent.self, // [!code ++]
                provider: IntentProvider() // [!code ++]
            ) { entry in // [!code ++]
                ConfigurableWidgetEntryView(entry: entry) // [!code ++]
            } // [!code ++]
        } // [!code ++]
    }
    ```
    </Step>

    <Step title="Getting Data from Flutter to the Configuration">
        With SiriKit Intents you need to add a new `IntentHandler` that can handle using Data you send from home_widget.

        For this add a new `Intents Extension` to your App. Using <kbd>File</kbd> > <kbd>New</kbd> > <kbd>Target</kbd> > <kbd>Intents Extension</kbd>

        <Image src="/assets/configurable/ios/siri-intents-extension.webp"/>

        In your SiriKit Intents definition add the newly created Extension as a Target
        
        <Image src="/assets/configurable/ios/siri-extension-target-membership.webp"/>

        In the same configurations file add a new Type and configure it to the options you need.

        <Image src="/assets/configurable/ios/siri-new-type.webp"/>

        <Image src="/assets/configurable/ios/siri-created-type.webp"/>

        Create a Field that uses this new type. Ensuring to enable <i>Options are provided dynamically</i>

        <Image src="/assets/configurable/ios/siri-select-type.webp"/>
        
        <Image src="/assets/configurable/ios/siri-extension-dynamic-option.webp"/>

        Implement the `IntentHandler` to handle the new Intent

        ```swift
        import Intents

        class IntentHandler: INExtension, GreetingIntentIntentHandling {
        
        func providePunctuationOptionsCollection(for intent: GreetingIntentIntent) async throws -> INObjectCollection<Punctuation> {
            let userDefaults = UserDefaults(suiteName: "YOUR_APP_GROUP")
            
            do {
                let jsonPunctuations = (userDefaults?.string(forKey: "punctuations") ?? "[\"!\"]").data(using: .utf8)!
                let stringArray = try JSONDecoder().decode([String].self, from: jsonPunctuations)
                let items = stringArray.map { punctuation in
                    Punctuation(identifier: punctuation, display: punctuation)
                }
                return INObjectCollection(items: items)
                
            } catch {
                return INObjectCollection(items: [Punctuation(identifier: "!", display: "!")])
            }
        }
        }
        ```

    </Step>
    <Step title="Seeing Configuration in Flutter">
    Similar to the `WidgetConfigurationIntent` you can use `HomeWidget.getInstalledWidgets()` to get the configuration of the Widget.

    ```dart
    final configuration = await HomeWidget.getInstalledWidgets();
    ```

    this will give you a list of `HomeWidgetInfo` in there there is a `Map<String, dynamic>? configuration`.


    For our Greeting Example the Configuration would be:

    ```json
    {
        "Name": "Siri",
        "Punctuation": {
            "identifier": "👋",
            "displayString": "👋"
        }
    }
    ```

    <div align="center" class="justify-center flex" style={{ gap: '8px', alignItems: 'center' }}>
            <Image src="/assets/configurable/ios/siri-configured-example.webp"/>
            <Image src="/assets/configurable/ios/siri-configuration-in-flutter.webp"/>
        </div>
    
    </Step>
</Steps>


## Android

Use a **dedicated `FlutterActivity`** for widget configuration and a **second Dart entry point** (e.g. `configureMain`) so configuration always runs in a clean isolate and you do not mix “open app” with “configure widget.” The steps below describe that setup.

For a full example, check out the [configurable_widget](https://github.com/ABausG/home_widget/tree/main/examples/configurable_widget) example app.

<Info>
    You can point `android:configure` at **`MainActivity`** and handle everything in **`main()`**, but that is easy to get wrong: `main()` runs once per process, while your activity can be **recreated**, **resumed**, or receive **new intents** when the user opens configuration again. You must detect “launched from widget configuration” in the right **lifecycle** moments—not only on the first `main()`—or you can show the wrong UI or miss a configure flow. Prefer the dedicated activity unless you have a strong reason not to.
</Info>

<Steps>
    <Step title="Create a configuration Activity">

        Subclass `FlutterActivity` and override the Dart entry point name so Flutter runs `configureMain` instead of `main`:

        ```kotlin
        package es.antonborri.configurable_widget

        import io.flutter.embedding.android.FlutterActivity

        class WidgetConfigurationActivity : FlutterActivity() {
          override fun getDartEntrypointFunctionName(): String = "configureMain"
        }
        ```

        Keep your launcher `MainActivity` on the default `main` entry point.

    </Step>

    <Step title="Register the Activity in the widget provider">

        In your widget’s `appwidget-provider` XML, set `reconfigurable` and point `android:configure` at the **fully qualified class name** of your configuration activity:

        ```xml
        <appwidget-provider
            xmlns:android="http://schemas.android.com/apk/res/android"
            ...
            android:widgetFeatures="reconfigurable"
            android:configure="com.example.app.WidgetConfigurationActivity" />
        ```

        This enables the widget’s configuration / “Settings” flow from the system UI.

    </Step>

    <Step title="Declare the Activity in AndroidManifest.xml">

        Register the configuration activity with an intent filter for `android.appwidget.action.APPWIDGET_CONFIGURE` (same action as [`AppWidgetManager.ACTION_APPWIDGET_CONFIGURE`](https://developer.android.com/reference/android/appwidget/AppWidgetManager#ACTION_APPWIDGET_CONFIGURE)). Mirror the Flutter embedding setup you use on `MainActivity` (`LaunchTheme`, `NormalTheme`, `configChanges`, etc.):

        ```xml
        <activity
            android:name=".WidgetConfigurationActivity"
            android:exported="true"
            android:launchMode="singleTop"
            ... >
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme" />
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
            </intent-filter>
        </activity>
        ```

    </Step>

    <Step title="Add `configureMain` and listen for configuration">

        Define a second entry point with `@pragma('vm:entry-point')` so tree shaking keeps it. The name must match `getDartEntrypointFunctionName()`:

        ```dart
        @pragma('vm:entry-point')
        Future<void> configureMain() async {
          WidgetsFlutterBinding.ensureInitialized();
          final configuredWidgetId =
              await HomeWidget.initiallyLaunchedFromHomeWidgetConfigure();

          if (configuredWidgetId != null) {
            return runApp(MaterialApp(
              home: YourConfigurationScreen(widgetId: configuredWidgetId),
            ));
          }
          return main();
        }
        ```

        Call `HomeWidget.initiallyLaunchedFromHomeWidgetConfigure()` as early as possible after `WidgetsFlutterBinding.ensureInitialized()`. If the user opened this activity to configure a widget, the `Future` resolves to that instance’s **widget ID** (string); otherwise `null` (you can fall through to `main()`).

        When a configure launch is detected, the plugin sets the activity result to `RESULT_CANCELED` until you finish successfully—so canceling or leaving the flow behaves correctly.

    </Step>

    <Step title="Build the configuration UI and persist with HomeWidget">

        In your configuration screen, use the widget ID to scope stored keys per instance (for example `name.$widgetId` and `punctuation.$widgetId`), read/write them with `HomeWidget.getWidgetData` / `HomeWidget.saveWidgetData`, and refresh the widget with `HomeWidget.updateWidget` (pass `qualifiedAndroidName` for your `AppWidgetProvider` / receiver class if needed).

    </Step>

    <Step title="Finish configuration">

        When the user saves (or you are done applying settings), call:

        ```dart
        await HomeWidget.finishHomeWidgetConfigure();
        ```

        This completes the configuration activity with `RESULT_OK`, returns the `EXTRA_APPWIDGET_ID` to the system, and closes the activity.

    </Step>
</Steps>

For platform details and UI expectations, see the [Android App Widget configuration guide](https://developer.android.com/develop/ui/views/appwidgets/configuration).