flutter_android_widgets 0.0.2 copy "flutter_android_widgets: ^0.0.2" to clipboard
flutter_android_widgets: ^0.0.2 copied to clipboard

Define Android home screen widgets entirely in Dart. No XML, no Kotlin, no manual manifest editing.

flutter_android_widgets

Define Android home screen widgets entirely in Dart.
No XML. No Kotlin. No manual manifest editing. Just Dart.

Quick StartLive UpdatesHow It WorksAPI ReferenceExample • Full Documentation


The Problem #

Adding a home screen widget to a Flutter app today requires you to:

  1. Write an XML layout file (res/layout/widget_*.xml)
  2. Write an XML metadata file (res/xml/appwidget_provider_*.xml)
  3. Write a Kotlin AppWidgetProvider class with SharedPreferences boilerplate
  4. Manually register a <receiver> in AndroidManifest.xml
  5. Wire up a SharedPreferences bridge so Flutter and native code share data

That's 4+ files in 3 different languages just to show a number on the home screen. Every time you change the layout, you have to update the XML, the Kotlin, and potentially the manifest — in lockstep.

The Solution #

final myWidget = AndroidWidget(
  info: WidgetInfo(
    widgetClassName: 'MyWidgetProvider',
    widgetName: 'My Widget',
    minWidth: 250,
    minHeight: 100,
    updateInterval: Duration(hours: 1),
  ),
  layout: WColumn(
    backgroundColor: '#1A1A2E',
    padding: 16,
    children: [
      WText('\${userName}', textSize: 18, bold: true, textColor: '#FFFFFF'),
      WText('\${lastUpdated}', textSize: 12, textColor: '#AAAAAA'),
      WButton(label: 'Refresh', actionKey: 'refresh'),
    ],
  ),
  dataKeys: ['userName', 'lastUpdated'],
);

Run dart run build_runner build and you're done. The package generates all four native files and patches your manifest automatically.


Quick Start #

1. Install #

# pubspec.yaml
dependencies:
  flutter_android_widgets: ^0.0.1

dev_dependencies:
  build_runner: ^2.4.0
flutter pub get

2. Add manifest markers #

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

<application ...>
    <activity ...>
        <!-- your existing activity -->
    </activity>

    <!-- flutter_android_widgets:start -->
    <!-- flutter_android_widgets:end -->
</application>

3. Define your widget #

Create a file (e.g. lib/my_widgets.dart):

import 'package:flutter_android_widgets/flutter_android_widgets.dart';

final myWidget = AndroidWidget(
  info: WidgetInfo(
    widgetClassName: 'StatsWidgetProvider',
    widgetName: 'App Stats',
    minWidth: 250,
    minHeight: 100,
    updateInterval: Duration(hours: 1),
    resizeMode: WidgetResizeMode.horizontal,
  ),
  layout: WColumn(
    backgroundColor: '#1A1A2E',
    padding: 16,
    children: [
      WText('\${title}', textSize: 18, bold: true, textColor: '#FFFFFF'),
      WText('\${subtitle}', textSize: 12, textColor: '#AAAAAA'),
      WButton(label: 'Refresh', actionKey: 'refresh'),
    ],
  ),
  dataKeys: ['title', 'subtitle'],
);

4. Generate #

dart run build_runner build

This creates:

Generated file Purpose
res/layout/widget_stats_widget_provider.xml RemoteViews-compatible XML layout
res/xml/appwidget_provider_stats_widget_provider.xml Widget size, update interval, resize mode
kotlin/.../StatsWidgetProvider.kt Full AppWidgetProvider with data bindings
AndroidManifest.xml <receiver> entry injected between markers

5. Push data from Flutter #

import 'package:flutter_android_widgets/flutter_android_widgets.dart';

await HomeWidgetData.saveAll({
  'title': 'Active Users',
  'subtitle': '1,247 today',
});

The widget on the home screen updates the next time Android refreshes it, or immediately when the user taps the Refresh button.

But there's a better way — see Live Updates to make data changes appear on the widget instantly.


Live Updates #

The package can automatically refresh the home screen widget whenever your Flutter app saves data, resumes from the background, or performs a hot restart — so you see changes on the widget in real time during development.

Setup #

Pass your widget definitions to WidgetUpdater.initialize() so that layout styling is synced to SharedPreferences on every hot restart:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  WidgetUpdater.initialize(widgets: [myWidget]); // ← pass your widget list
  HomeWidgetData.autoUpdate = true;              // ← auto-refresh after every save
  runApp(const MyApp());
}

How it works #

Trigger What happens
HomeWidgetData.save() / saveAll() With autoUpdate = true, automatically calls WidgetUpdater.updateAll() after saving
Hot restart (Ctrl+Shift+F5) main() re-runs → styles synced to SharedPreferences → updateAll() → widget refreshes
App resumes from background Styles re-synced → updateAll() → widget shows latest styling and data

What updates on hot restart #

Hot restart re-runs main(), which re-syncs all style properties to SharedPreferences before triggering a widget refresh. Hot reload does not work for layout changes — it does not re-evaluate top-level final variables.

✅ Updates immediately on hot restart

These properties are read from SharedPreferences at widget update time via the RemoteViews API:

Property Applies to
backgroundColor All nodes (containers, text, buttons, images, progress bars)
padding All nodes
gravity WColumn, WRow, WText
textColor WText, WButton
textSize WText, WButton
maxLines WText
label (button text) WButton
progress WProgressBar
Data values (via HomeWidgetData.save) WText with ${key} bindings

❌ Requires rebuild + re-add widget (cannot update at runtime)

These properties are compiled into the APK and cannot be changed by any runtime API:

Property Why it can't update at runtime
minWidth / minHeight Stored in appwidget_provider_*.xml — a compiled APK resource. The Android launcher reads this once when the widget is first placed to determine its grid cell size. No Android API exists to change widget dimensions after placement.
resizeMode Same as above — part of the provider XML metadata read by the launcher.
updatePeriodMillis Registered as a system alarm from the provider XML. Cannot be changed without reinstalling.
widgetName The label shown in the widget picker comes from AndroidManifest.xml.
bold (text style) RemoteViews has no API to change Typeface/text style at runtime.

To apply any of the above changes:

  1. Modify my_widgets.dart
  2. Run dart run build_runner build
  3. Run flutter run (reinstalls the APK with new compiled values)
  4. For minWidth/minHeight/resizeMode: remove the widget from the home screen and re-add it — the launcher caches the old size at placement time

Manual updates #

You can also trigger updates manually:

// Update all widgets
await WidgetUpdater.updateAll();

// Update a specific widget
await WidgetUpdater.updateWidget('MyWidgetProvider');

What gets generated #

When you run build_runner, the package also generates:

File Purpose
FlutterAndroidWidgetsChannel.kt MethodChannel handler that sends APPWIDGET_UPDATE broadcasts
MainActivity.kt (patched) Registers the channel in configureFlutterEngine

How It Works #

┌──────────────────────────────────────────────────────────────┐
│                        YOUR DART CODE                        │
│                                                              │
│   AndroidWidget(                                             │
│     info: WidgetInfo(...),                                   │
│     layout: WColumn([ WText(...), WButton(...) ]),           │
│     dataKeys: ['key1', 'key2'],                              │
│   )                                                          │
└─────────────────────────┬────────────────────────────────────┘
                          │
                    build_runner
                          │
            ┌─────────────┼──────────────┐
            ▼             ▼              ▼
    ┌──────────────┐ ┌──────────┐ ┌──────────────┐
    │ XmlGenerator │ │ Kotlin   │ │ Manifest     │
    │              │ │ Generator│ │ Patcher      │
    │ • layout.xml │ │          │ │              │
    │ • provider   │ │ • .kt    │ │ • <receiver> │
    │   .xml       │ │   class  │ │   injection  │
    └──────────────┘ └──────────┘ └──────────────┘
            │             │              │
            ▼             ▼              ▼
    ┌──────────────────────────────────────────────┐
    │              ANDROID PROJECT                  │
    │  res/layout/   res/xml/   kotlin/   Manifest │
    └──────────────────────────────────────────────┘

The pipeline (what happens when you run build_runner):

  1. WidgetAnalyzer scans your Dart source files using regex-based pattern matching. It finds every final/const variable of type AndroidWidget and extracts the metadata (WidgetInfo) and the full layout tree.

  2. NodeCollector walks the layout tree and assigns unique Android view IDs, extracts ${key} data bindings from text nodes, collects action keys from buttons, and catalogs drawable references from images.

  3. XmlGenerator takes the node tree and produces:

    • A res/layout/widget_<name>.xml file with proper Android namespaces, only using RemoteViews-compatible elements
    • A res/xml/appwidget_provider_<name>.xml metadata file
  4. KotlinGenerator produces a complete AppWidgetProvider Kotlin class that:

    • Reads from FlutterSharedPreferences using the exact same key prefix Flutter uses
    • Binds ${key} placeholders to TextView content via RemoteViews.setTextViewText()
    • Wires up PendingIntent broadcast receivers for button clicks
    • Handles onReceive() to re-read SharedPreferences and update the widget
  5. ManifestPatcher injects <receiver> XML blocks between the <!-- flutter_android_widgets:start --> / <!-- flutter_android_widgets:end --> marker comments — including all necessary <intent-filter> and <meta-data> entries.

The data bridge at runtime:

Flutter App                              Android Widget
    │                                         ▲
    │  HomeWidgetData.save('key', 'value')    │
    │          │                              │
    ▼          ▼                              │
  SharedPreferences ──────────────────────────┘
  (FlutterSharedPreferences XML file)
  Key: "flutter.flutter_android_widgets_key"

Flutter writes via shared_preferences. The generated Kotlin reads from the same FlutterSharedPreferences file using the key prefix flutter.flutter_android_widgets_. No platform channels, no method calls, no serialization — just a shared file.


API Reference #

Core Classes #

AndroidWidget

The top-level definition. One variable = one home screen widget.

const AndroidWidget({
  required WidgetInfo info,       // metadata (name, size, interval)
  required WidgetNode layout,     // the visual tree
  List<String> dataKeys = const [],  // keys for data binding
})

WidgetInfo

Metadata that maps to appwidget_provider.xml attributes.

const WidgetInfo({
  required String widgetClassName,    // Kotlin class name (PascalCase)
  required String widgetName,         // Label in the widget picker
  required int minWidth,              // Minimum width in dp
  required int minHeight,             // Minimum height in dp
  required Duration updateInterval,   // Auto-refresh interval (min 30 min)
  WidgetResizeMode resizeMode = WidgetResizeMode.both,
  String? previewImage,               // Manual drawable name for widget picker preview
  String? previewImagePath,           // Path to a local image file — auto-copied to res/drawable/
})

See Widget Picker Preview for details on both fields.

HomeWidgetData

Runtime API for pushing data from Flutter to the widget.

Method Description
HomeWidgetData.save(key, value) Save a single String value
HomeWidgetData.read(key) Read the current value (returns String?)
HomeWidgetData.remove(key) Delete a key
HomeWidgetData.saveAll(map) Save multiple key-value pairs at once
HomeWidgetData.autoUpdate Set to true to auto-refresh widgets after every save

WidgetUpdater

Triggers widget refreshes from Flutter via a MethodChannel.

Method Description
WidgetUpdater.initialize({widgets}) Enable auto-refresh on hot restart & app resume; pass widget list to sync styles
WidgetUpdater.updateAll() Send update broadcast to all widget providers
WidgetUpdater.updateWidget(name) Update a specific widget by class name

Layout Primitives #

Containers

Class Android View Description
WColumn LinearLayout (vertical) Stack children vertically
WRow LinearLayout (horizontal) Arrange children horizontally
WStack FrameLayout Layer children on top of each other

All containers accept: children, padding, backgroundColor, id, gravity (WColumn/WRow only).

Leaf Views

Class Android View Key Properties
WText TextView text, textSize, textColor, bold, maxLines, gravity
WButton Button label, actionKey, textSize, textColor
WImage ImageView drawableName, width, height, contentDescription
WProgressBar ProgressBar horizontal, progress, width, height

All leaf views accept: id, padding, backgroundColor.

Data Binding #

Use ${key} in any WText to bind it to a SharedPreferences value:

WText('\${userName}')                           // Simple binding
WText('Hello, \${name}! You have \${count}.')   // Template binding

The Kotlin generator automatically reads the right SharedPreferences key and replaces the placeholder at widget update time.

Resize Modes #

WidgetResizeMode.none        // Fixed size
WidgetResizeMode.horizontal  // Resizable horizontally only
WidgetResizeMode.vertical    // Resizable vertically only
WidgetResizeMode.both        // Resizable in both directions (default)

Widget Picker Preview #

Android shows a preview image in the widget picker when the user long-presses the home screen. There are two ways to provide one:

Point to any image in your project. build_runner copies it to res/drawable/ automatically:

final myWidget = AndroidWidget(
  info: WidgetInfo(
    widgetClassName: 'MyWidgetProvider',
    widgetName: 'My Widget',
    minWidth: 250,
    minHeight: 100,
    updateInterval: Duration(hours: 1),
    previewImagePath: 'assets/previews/my_widget.png', // ← path relative to project root
  ),
  layout: WColumn(...),
  dataKeys: [...],
);

The file is copied to android/app/src/main/res/drawable/widget_preview_my_widget_provider.png and android:previewImage is set automatically.

Any Android-supported format works: PNG, JPG, WEBP.

Option B — previewImage (manual)

If you've already placed an image in res/drawable/ yourself, reference its name without extension:

previewImage: 'my_existing_drawable',  // references res/drawable/my_existing_drawable.png

If both are set, previewImagePath takes precedence — it overwrites previewImage with the auto-derived drawable name.


Example #

The example/ directory contains a complete, runnable Flutter app that demonstrates the full workflow:

  • example/lib/my_widgets.dart — widget definition in Dart
  • example/lib/main.dart — Flutter UI that pushes data to the widget
cd example
flutter pub get
dart run build_runner build
flutter run

After installing, long-press your home screen → Widgets → find "Widget Demo" → add it. Then use the app to update the widget's data in real time.


Multiple Widgets #

Define as many widgets as you want — each top-level AndroidWidget variable becomes its own widget:

final weatherWidget = AndroidWidget(
  info: WidgetInfo(
    widgetClassName: 'WeatherWidgetProvider',
    widgetName: 'Weather',
    minWidth: 250, minHeight: 100,
    updateInterval: Duration(hours: 1),
  ),
  layout: WColumn(children: [
    WText('\${temperature}', textSize: 32, bold: true, textColor: '#FFFFFF'),
    WText('\${condition}', textSize: 14, textColor: '#CCCCCC'),
  ]),
  dataKeys: ['temperature', 'condition'],
);

final todoWidget = AndroidWidget(
  info: WidgetInfo(
    widgetClassName: 'TodoWidgetProvider',
    widgetName: 'Todo List',
    minWidth: 250, minHeight: 200,
    updateInterval: Duration(minutes: 30),
  ),
  layout: WColumn(children: [
    WText('Tasks', textSize: 18, bold: true),
    WText('\${task1}', textSize: 14),
    WText('\${task2}', textSize: 14),
    WText('\${task3}', textSize: 14),
  ]),
  dataKeys: ['task1', 'task2', 'task3'],
);

Each generates its own XML, Kotlin, and manifest entry.


Test Coverage #

94 unit tests across 6 files:

Test file Tests
xml_generator_test.dart 13
kotlin_generator_test.dart 8
manifest_patcher_test.dart 9
widget_analyzer_test.dart 16
data_model_test.dart 37
channel_generator_test.dart 12

Run with flutter test from the package root.


Constraints #

Android home screen widgets are rendered using RemoteViews, which has strict limitations:

  • Limited views — Only the views listed in the primitives table are supported. No RecyclerView, no custom views, no Canvas drawing.
  • No interactivity beyond clicks — Buttons can trigger broadcasts, but there's no text input, no scroll (on older APIs), and no animations.
  • 30-minute minimum update interval — Android enforces this. The package clamps any shorter interval to 30 minutes. For more frequent updates, trigger updates from your app via AppWidgetManager.
  • SharedPreferences data types — Data binding values must be strings. Convert numbers, dates, etc. to strings before saving.
  • Build runner required — You must run dart run build_runner build after changing your widget definition. The generated files are checked into source control.

Project Structure #

your_app/
├── lib/
│   └── my_widgets.dart         ← You write this
├── android/app/src/main/
│   ├── AndroidManifest.xml     ← Add markers once, auto-patched after
│   ├── res/
│   │   ├── layout/
│   │   │   └── widget_*.xml    ← Generated
│   │   └── xml/
│   │       └── appwidget_provider_*.xml  ← Generated
│   └── kotlin/.../
│       ├── *Provider.kt        ← Generated
│       ├── FlutterAndroidWidgetsChannel.kt  ← Generated
│       └── MainActivity.kt     ← Patched (adds MethodChannel registration)
└── pubspec.yaml

License #

BSD 3-Clause. See LICENSE for details.

1
likes
140
points
42
downloads

Publisher

verified publisheranexon.tech

Weekly Downloads

Define Android home screen widgets entirely in Dart. No XML, no Kotlin, no manual manifest editing.

Homepage
Repository (GitHub)

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

build, flutter, shared_preferences

More

Packages that depend on flutter_android_widgets