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-skillsThis skill uses the workspace's default tool permissions.
- [Architecture & Data Flow](#architecture--data-flow)
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
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
)
}