Help us improve
Share bugs, ideas, or general feedback.
From flutter-skills
Adds home screen widgets to a Flutter app for Android and iOS. Use when providing glanceable app information or quick actions on the device home screen.
npx claudepluginhub gsmlg-dev/code-agent --plugin flutter-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/flutter-skills:flutter-adding-home-screen-widgetsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- [Architecture & Data Flow](#architecture--data-flow)
Guides expert Flutter 3.x and Dart 3.x development for multi-platform apps, including advanced widgets, state management with Riverpod/Bloc/GetX, performance optimization, and architecture patterns.
Provides Flutter patterns for widget architecture, state management, Impeller renderer, and platform-adaptive design, avoiding common mistakes.
Implements home screen, Lock Screen, StandBy widgets, Live Activities with Dynamic Island, and Control Center controls using WidgetKit and ActivityKit. Covers timeline providers, interactive widgets, and extension setup.
Share bugs, ideas, or general feedback.
Home Screen Widgets require native UI implementation (SwiftUI for iOS, XML/Kotlin for Android). The Flutter app communicates with these native widgets via shared local storage (UserDefaults on iOS, SharedPreferences on Android) using the home_widget package.
Use this checklist to implement the Dart side of the Home Screen Widget integration.
HomeWidget.setAppGroupId('<YOUR_APP_GROUP>') in initState() or app startup.HomeWidget.saveWidgetData<T>('key', value) to write data to shared storage.HomeWidget.updateWidget(iOSName: 'YourIOSWidget', androidName: 'YourAndroidWidget') to notify the OS.If targeting iOS, implement the widget using Xcode and SwiftUI.
ios/Runner.xcworkspace in Xcode. Add a new Widget Extension target. Disable "Include Live Activity" and "Include Configuration Intent" unless explicitly required.TimelineEntry to hold the data passed from shared storage.getSnapshot and getTimeline, instantiate UserDefaults(suiteName: "<YOUR_APP_GROUP>").userDefaults?.string(forKey: "your_key").TimelineEntry.View to display the data from the TimelineEntry.If targeting Android, implement the widget using Android Studio and XML/Kotlin.
android folder in Android Studio. Right-click the app directory -> New -> Widget -> App Widget.res/layout/<widget_name>.xml to define the UI using standard Android XML layouts (e.g., RelativeLayout, TextView, ImageView).AppWidgetProvider.onUpdate method, retrieve shared data using HomeWidgetPlugin.getData(context).widgetData.getString("your_key", null).RemoteViews and setTextViewText or setImageViewBitmap.appWidgetManager.updateAppWidget(appWidgetId, views).If the UI is too complex to recreate natively (e.g., custom charts), render the Flutter widget to an image and display the image in the native widget.
GlobalKey.HomeWidget.renderFlutterWidget(), passing the widget, a filename, and the key.UserDefaults and render using UIImage(contentsOfFile:) inside a SwiftUI Image.SharedPreferences, decode using BitmapFactory.decodeFile(), and render using setImageViewBitmap().If utilizing custom fonts defined in Flutter on iOS Home Screen Widgets:
CTFontManagerRegisterFontsForURL.Font.custom().import 'package:home_widget/home_widget.dart';
const String appGroupId = 'group.com.example.app';
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget';
Future<void> updateWidgetData(String title, String description) async {
await HomeWidget.setAppGroupId(appGroupId);
await HomeWidget.saveWidgetData<String>('headline_title', title);
await HomeWidget.saveWidgetData<String>('headline_description', description);
await HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
}
import WidgetKit
import SwiftUI
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description: String
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Loading...", description: "Loading...")
}
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let userDefaults = UserDefaults(suiteName: "group.com.example.app")
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description"
let entry = NewsArticleEntry(date: Date(), title: title, description: description)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
getSnapshot(in: context) { (entry) in
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading) {
Text(entry.title).font(.headline)
Text(entry.description).font(.subheadline)
}
}
}
package com.example.app.widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
import com.example.app.R
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", "No Title")
setTextViewText(R.id.headline_title, title)
val description = widgetData.getString("headline_description", "No Description")
setTextViewText(R.id.headline_description, description)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
// Add this to your SwiftUI View struct
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
init(entry: Provider.Entry) {
self.entry = entry
CTFontManagerRegisterFontsForURL(
bundle.appending(path: "/fonts/YourCustomFont.ttf") as CFURL,
CTFontManagerScope.process,
nil
)
}