diff --git a/.github/.cspell/names_dictionary.txt b/.github/.cspell/names_dictionary.txt
new file mode 100644
index 00000000..ac6fde7d
--- /dev/null
+++ b/.github/.cspell/names_dictionary.txt
@@ -0,0 +1,18 @@
+# specific people's names and/or usernames
+mchudy # github.com/mchudy
+milindgoel15 # github.com/milindgoel15
+mattrltrent # github.com/mattrltrent
+eliasto # github.com/eliasto
+leighajarett # github.com/leighajarett
+ColinSchmale # github.com/ColinSchmale
+linziyou # github.com/linziyou0601
+roly # github.com/roly151
+aaronkelton # github.com/aaronkelton
+hadysata # github.com/hadysata
+Verbeeck # github.com/NicolaVerbeeck
+ronnieeeeee # github.com/ronnieeeeee
+josepedromonteiro # github.com/josepedromonteiro
+stepushchik # github.com/stepushchik-denis-gismart
+gismart # github.com/stepushchik-denis-gismart
+aljkor # github.com/aljkor
+mgonzalezc # github.com/mgonzalezc
\ No newline at end of file
diff --git a/.github/cspell.yaml b/.github/cspell.yaml
new file mode 100644
index 00000000..da51c308
--- /dev/null
+++ b/.github/cspell.yaml
@@ -0,0 +1,66 @@
+version: "0.2"
+ignorePaths:
+ [
+ "**/coverage",
+ "**/*.xcscheme",
+ "**/*.storyboard",
+ "**/GeneratedPluginRegistrant.m",
+ "**/GeneratedPluginRegistrant.java",
+ "**/*.pbxproj",
+ "**/*.xcconfig",
+ "**/**/*.podspec",
+ "**/Info.plist",
+ "**/*.xcassets",
+ "**/*.xcworkspace",
+ "**/*.xcodeproj",
+ "**/Pods/Target Support Files/",
+ "**/Pods/Local Podspecs",
+ "**/gradlew",
+ "**/gradlew.bat",
+ "**/gradle-wrapper.properties",
+ "**/AppFrameworkInfo.plist",
+ "**/gradle.properties",
+ "cspell.yaml",
+ "**/build.gradle",
+ "**/proguard-rules.pro",
+ ]
+dictionaries:
+ - names
+dictionaryDefinitions:
+ - name: names
+ path: .cspell/names_dictionary.txt
+words:
+ - antonborries
+ - Borries
+ - abausg
+ - antonborri
+ - mocktail
+ - Goldens
+ - cupertino
+ - workmanager
+ - cocoapods
+ - renderview
+ - appex
+ - pinability
+ - chào
+ - titleclicked
+ - NSURL
+ - xcconfig
+ - podhelper
+ - podfile
+ - realpath
+ - appwidget
+ - Millis # abbreviation for Milliseconds
+ - androidx
+ - Jetpack
+ - kotlinx
+ - lateinit
+ - prefs # short for preferences
+ - mipmap # android image format
+ - nullsafety
+ - Deeplinks
+ - Deque
+ - ktfmt
+ - libexec
+ - SRCROOT
+ - Codelab
diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml
new file mode 100644
index 00000000..9b9b645d
--- /dev/null
+++ b/.github/workflows/spellcheck.yml
@@ -0,0 +1,17 @@
+name: Spellcheck
+
+on:
+ pull_request:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: streetsidesoftware/cspell-action@v6
+ with:
+ config: .github/cspell.yaml
\ No newline at end of file
diff --git a/docs.json b/docs.json
new file mode 100644
index 00000000..debbde90
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,95 @@
+{
+ "name": "home_widget",
+ "theme": "##567acb",
+ "twitter": "abausg",
+ "sidebar": [
+ [
+ "Overview",
+ "/"
+ ],
+ [
+ "Usage",
+ [
+ [
+ "Sync Data",
+ "/usage/sync-data"
+ ],
+ [
+ "Update Widget",
+ "/usage/update-widget"
+ ]
+ ]
+ ],
+ [
+ "Platform Setup",
+ [
+ [
+ "iOS",
+ "/setup/ios"
+ ],
+ [
+ "Android",
+ "/setup/android"
+ ]
+ ]
+ ],
+ [
+ "Features",
+ [
+ [
+ "Render Flutter Widgets",
+ "/features/render-flutter-widgets"
+ ],
+ [
+ "Interactive Widgets",
+ "/features/interactive-widgets"
+ ],
+ [
+ "Detect Clicks",
+ "/features/detect-clicks"
+ ],
+ [
+ "Analytics",
+ "/features/analytics"
+ ],
+ [
+ "iOS Lock Screen Widgets",
+ "/features/ios-lock-screen"
+ ],
+ [
+ "Pin Widget (Android)",
+ "/features/pin-widget"
+ ],
+ [
+ "Background Updates",
+ "/features/background-updates"
+ ]
+ ]
+ ],
+ [
+ "Android XML",
+ [
+ [
+ "Overview",
+ "/android-xml/overview"
+ ],
+ [
+ "Setup",
+ "/android-xml/setup"
+ ],
+ [
+ "Render Flutter Widgets",
+ "/android-xml/render-flutter-widgets"
+ ],
+ [
+ "Interactive Widgets",
+ "/android-xml/interactive-widgets"
+ ],
+ [
+ "Detect Clicks",
+ "/android-xml/detect-clicks"
+ ]
+ ]
+ ]
+ ]
+}
diff --git a/docs/android-xml/detect-clicks.mdx b/docs/android-xml/detect-clicks.mdx
new file mode 100644
index 00000000..a3d4a11e
--- /dev/null
+++ b/docs/android-xml/detect-clicks.mdx
@@ -0,0 +1,22 @@
+---
+title: Detect Clicks
+description: Detecting Clicks on Android Widgets
+---
+
+# Detect Clicks on with Android XML Widgets
+
+Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest`
+```
+
+
+
+```
+
+In your WidgetProvider add a PendingIntent to your View using `HomeWidgetLaunchIntent.getActivity`
+```kotlin
+val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
+ context,
+ MainActivity::class.java,
+ Uri.parse("homeWidgetExample://message?message=$message"))
+setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData)
+```
\ No newline at end of file
diff --git a/docs/android-xml/interactive-widgets.mdx b/docs/android-xml/interactive-widgets.mdx
new file mode 100644
index 00000000..ca54d0fc
--- /dev/null
+++ b/docs/android-xml/interactive-widgets.mdx
@@ -0,0 +1,27 @@
+---
+title: Interactive Widgets
+description: How to create interactive Widgets with home_widget and Android XML Widgets
+---
+
+# Interactive Widgets
+
+Follow the necessary steps to set up everything on the Flutter Side as described [here](/features/interactive-widgets#flutter-setup).
+
+1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file
+ ```
+
+
+
+
+
+
+ ```
+2. Add a `HomeWidgetBackgroundIntent.getBroadcast` PendingIntent to the View you want to add a click listener to
+ ```kotlin
+ val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
+ context,
+ Uri.parse("homeWidgetExample://titleClicked")
+ )
+ setOnClickPendingIntent(R.id.widget_title, backgroundIntent)
+ ```
diff --git a/docs/android-xml/overview.mdx b/docs/android-xml/overview.mdx
new file mode 100644
index 00000000..233459e7
--- /dev/null
+++ b/docs/android-xml/overview.mdx
@@ -0,0 +1,12 @@
+---
+title: Android XML
+description: home_widget and Android XML Widgets
+---
+
+# Android XML Support
+
+home_widget supports Android Widgets created with XML. This allows you to create Widgets for your Android App using the home_widget Plugin.
+However going forward it is recommended to use the [Jetpack Glance](/setup/android) way to create Widgets home_widget.
+
+In the following pages you can still find information on how to use respective Features of home_widget with Android XML Widgets.
+
diff --git a/docs/android-xml/render-flutter-widget.mdx b/docs/android-xml/render-flutter-widget.mdx
new file mode 100644
index 00000000..357691eb
--- /dev/null
+++ b/docs/android-xml/render-flutter-widget.mdx
@@ -0,0 +1,58 @@
+---
+title: Render Flutter Widget
+description: Render Flutter Widgets in Android XML Widgets
+---
+
+# Render Flutter Widgets
+
+1. Add an image UI element to your xml file:
+ ```xml
+
+ ```
+2. Update your Kotlin code to get the chart image and put it into the widget, if it exists.
+ ```kotlin
+ class NewsWidget : AppWidgetProvider() {
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray,
+ ) {
+ for (appWidgetId in appWidgetIds) {
+ // Get reference to SharedPreferences
+ val widgetData = HomeWidgetPlugin.getData(context)
+ val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
+ // Get chart image and put it in the widget, if it exists
+ val imagePath = widgetData.getString("lineChart", null)
+ val imageFile = File(imagePath)
+ val imageExists = imageFile.exists()
+ if (imageExists) {
+ val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
+ setImageViewBitmap(R.id.widget_image, myBitmap)
+ } else {
+ println("image not found!, looked @: $imagePath")
+ }
+ // End new code
+ }
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+ }
+ }
+ }
+ ```
\ No newline at end of file
diff --git a/docs/android-xml/setup.mdx b/docs/android-xml/setup.mdx
new file mode 100644
index 00000000..c40b4fbc
--- /dev/null
+++ b/docs/android-xml/setup.mdx
@@ -0,0 +1,96 @@
+---
+title: Android XML Setup
+description: Setup home_widget and Widgets with Android XML
+---
+
+# Android XML Setup
+
+## Necessary Files
+
+### Widget Configuration
+In `android/app/src/main/res/xml` you need to create a configuration file.
+In here you can configure properties used for things like size constraints and preview layouts.
+```xml
+
+
+```
+For more Information on the possible contents of this File check the official Android Documentation [here](https://developer.android.com/develop/ui/views/appwidgets#AppWidgetProviderInfo)
+
+
+### Widget Layout
+Create Widget Layout inside `android/app/src/main/res/layout`
+
+This file contains the Layout of your Widget. Note that this can only use RemoteViews to build the Layout.
+
+```xml
+
+
+
+
+```
+
+### WidgetProvider
+The `WidgetProvider` is used to bind Data to the Layout.
+For convenience, you can extend from `HomeWidgetProvider` which gives you access to a SharedPreferences Object with the Data in the `onUpdate` method.
+
+```kotlin
+// Remember to set a package in order for home_widget to find the Provider
+package es.antonborri.home_widget_counter
+
+class CounterWidgetProvider : HomeWidgetProvider() {
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray,
+ widgetData: SharedPreferences) {
+ appWidgetIds.forEach { widgetId ->
+ val views = RemoteViews(context.packageName, R.layout.counter_widget).apply {
+ val count = widgetData.getInt("counter", 0)
+ setTextViewText(R.id.text_counter, count.toString())
+ }
+ // This line is important to trigger the update
+ appWidgetManager.updateAppWidget(widgetId, views)
+ }
+ }
+}
+```
+
+In case you don't want to use the convenience Method you can access the Data using
+```kotlin
+import es.antonborri.home_widget.HomeWidgetPlugin
+...
+HomeWidgetPlugin.getData(context)
+```
+which will give you access to the same SharedPreferences
+
+### Add WidgetReceiver to AndroidManifest
+```xml
+
+
+
+
+
+
+```
+
+## More Information
+For more Information on how to create and configure Android Widgets, check out [this guide](https://developer.android.com/develop/ui/views/appwidgets) on the Android Developers Page.
diff --git a/docs/assets/lockscreen.webp b/docs/assets/lockscreen.webp
new file mode 100644
index 00000000..69d40bfe
Binary files /dev/null and b/docs/assets/lockscreen.webp differ
diff --git a/docs/assets/pin-widget.gif b/docs/assets/pin-widget.gif
new file mode 100644
index 00000000..5167107b
Binary files /dev/null and b/docs/assets/pin-widget.gif differ
diff --git a/docs/features/analytics.mdx b/docs/features/analytics.mdx
new file mode 100644
index 00000000..c75a0475
--- /dev/null
+++ b/docs/features/analytics.mdx
@@ -0,0 +1,18 @@
+---
+title: Analytics
+description: Check which Widgets Users have currently installed
+---
+
+# Analytics
+
+Sometimes it can be useful to know which Widgets your Users have currently installed.
+
+Using home_widget you can check which Widgets a User currently has added to their HomeScreen
+
+```dart
+final List info = await HomeWidget.getInstalledWidgets();
+```
+
+This will give you respective Information about the Widgets a User has currently installed with respective platform specific Widget Information filled.
+
+Note that each combination of `iOSFamily`(Size) and `iOSKind`(Widget Type) will only appear once in the list due to limitations of iOS.
diff --git a/docs/features/background-updates.mdx b/docs/features/background-updates.mdx
new file mode 100644
index 00000000..044f6b9c
--- /dev/null
+++ b/docs/features/background-updates.mdx
@@ -0,0 +1,17 @@
+---
+title: Background Update
+description: Update your Widget while the App is in the Background
+---
+
+# Background Update
+As the methods of HomeWidget are static it is possible to use HomeWidget in the background to update the Widget even when the App is in the background.
+
+The example App is using the [flutter_workmanager](https://pub.dev/packages/workmanager) plugin to achieve this.
+Please follow the Setup Instructions for flutter_workmanager (or your preferred background code execution plugin). Most notably make sure that Plugins get registered in iOS in order to be able to communicate with the HomeWidget Plugin.
+In case of flutter_workmanager this achieved by adding:
+```swift
+WorkmanagerPlugin.setPluginRegistrantCallback { registry in
+ GeneratedPluginRegistrant.register(with: registry)
+}
+```
+to [AppDelegate.swift](example/ios/Runner/AppDelegate.swift)
\ No newline at end of file
diff --git a/docs/features/detect-clicks.mdx b/docs/features/detect-clicks.mdx
new file mode 100644
index 00000000..b4ff2122
--- /dev/null
+++ b/docs/features/detect-clicks.mdx
@@ -0,0 +1,66 @@
+---
+title: Detect Clicks
+description: Detect Clicks on Widgets from inside your App
+---
+
+# Detect Clicks
+
+home_widget offers functionality to check if your App was launched by clicking on an element in the HomeScreen Widget.
+
+
+Note that this is not for Interactive Widgets. For this please check the relevant documentation [here](/features/interactive-widgets).
+
+
+To detect this in your App there are two methods:
+
+For when your App is started from the very background check:
+```dart
+HomeWidget.initiallyLaunchedFromHomeWidget();
+```
+this will return a `Future` with the Uri that was used to start the App.
+
+When your App is already running all Widget clicks are broadcast to a Stream provided via:
+
+```dart
+HomeWidget.widgetClicked;
+````
+which is a `Stream` that you can listen to to detect App Launches from the Widget.title
+
+
+## Platform Setup
+
+In the following section you can learn how to add the necessary configurations to your native Widgets in order for clicks to bet detectable by home_widget.title
+
+### iOS
+
+Add `.widgetUrl` to your WidgetComponent
+```swift
+Text(entry.message)
+ .font(.body)
+ .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget"))
+```
+In order to only detect Widget Links you need to add the queryParameter`homeWidget` to the URL
+
+### Android
+
+
+Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest`
+```
+
+
+
+```
+
+Add the following modifier to your Widget (import from HomeWidget)
+```kotlin
+Text(
+ message,
+ style = TextStyle(fontSize = 18.sp),
+ modifier = GlanceModifier.clickable(
+ onClick = actionStartActivity(
+ context,
+ Uri.parse("homeWidgetExample://message?message=$message")
+ )
+ )
+)
+```
\ No newline at end of file
diff --git a/docs/features/interactive-widgets.mdx b/docs/features/interactive-widgets.mdx
new file mode 100644
index 00000000..29ab6a44
--- /dev/null
+++ b/docs/features/interactive-widgets.mdx
@@ -0,0 +1,137 @@
+---
+title: Interactive Widgets
+description: Build Interactive Widgets that call Flutter Code from Native Widgets
+---
+
+# Interactive Widgets
+
+Android and iOS (starting with iOS 17) allow widgets to have interactive Elements like Buttons.
+Using home_widget you can register Dart Callbacks that get invoked from the Native Widgets so you only have to write that logic once.
+
+
+
+
+
+## Flutter Setup
+
+1. Write a **static** function that takes a Uri as an argument. This will get called when a user clicks on the View
+ ```dart
+ @pragma("vm:entry-point")
+ FutureOr backgroundCallback(Uri data) async {
+ // do something with data
+ ...
+ }
+ ```
+ `@pragma('vm:entry-point')` must be placed above the `callback` function to avoid tree shaking in release mode.
+
+2. Register the callback function by calling
+ ```dart
+ HomeWidget.registerInteractivityCallback(backgroundCallback);
+ ```
+
+## iOS Setup
+
+1. Adjust your Podfile to add `home_widget` as a dependency to your WidgetExtension
+ ```rb
+ target 'YourWidgetExtension' do
+ use_frameworks!
+ use_modular_headers!
+
+ pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
+ end
+ ```
+2. To be able to use plugins with the Background Callback add this to your AppDelegate's `application` function
+ ```swift
+ if #available(iOS 17, *) {
+ HomeWidgetBackgroundWorker.setPluginRegistrantCallback { registry in
+ GeneratedPluginRegistrant.register(with: registry)
+ }
+ }
+ ```
+3. Create a custom `AppIntent` in your App Target (Runner) and make sure to select both your App and your WidgetExtension in the Target Membership panel
+
+ 
+
+ In this Intent you should import `home_widget` and call `HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)` in the perform method. `url` and `appGroup` can be either hardcoded or set as parameters from the Widget
+ ```swift
+ import AppIntents
+ import Flutter
+ import Foundation
+ import home_widget
+
+ @available(iOS 16, *)
+ public struct BackgroundIntent: AppIntent {
+ static public var title: LocalizedStringResource = "HomeWidget Background Intent"
+
+ @Parameter(title: "Widget URI")
+ var url: URL?
+
+ @Parameter(title: "AppGroup")
+ var appGroup: String?
+
+ public init() {}
+
+ public init(url: URL?, appGroup: String?) {
+ self.url = url
+ self.appGroup = appGroup
+ }
+
+ public func perform() async throws -> some IntentResult {
+ await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
+
+ return .result()
+ }
+ }
+ ```
+4. Add a Button to your Widget. This Button might be encapsulated by a Version check. Pass in an instance of the `AppIntent` created in the previous step
+ ```swift
+ Button(
+ intent: BackgroundIntent(
+ url: URL(string: "homeWidgetExample://titleClicked"), appGroup: widgetGroupId)
+ ) {
+ Text(entry.title).bold().font( /*@START_MENU_TOKEN@*/.title /*@END_MENU_TOKEN@*/)
+ }.buttonStyle(.plain)
+ ```
+5. With the current setup the Widget is now Interactive as long as the App is still in the background. If you want to have the Widget be able to wake the App up you need to add the following to your `AppIntent` file
+ ```swift
+ @available(iOS 16, *)
+ @available(iOSApplicationExtension, unavailable)
+ extension BackgroundIntent: ForegroundContinuableIntent {}
+ ```
+ This code tells the system to always perform the Intent in the App and not in a process attached to the Widget. Note however that this will start your Flutter App using the normal main entrypoint meaning your full app might be run in the background. To counter this you should add checks in the very first Widget you build inside `runApp` to only perform necessary calls/setups while the App is launched in the background
+
+
+
+## Android Setup
+
+1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file
+ ```
+
+
+
+
+
+
+ ```
+2. Create a custom Action
+ ```kotlin
+ class InteractiveAction : ActionCallback {
+ override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
+ val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("homeWidgetExample://titleClicked"))
+ backgroundIntent.send()
+ }
+ }
+ ```
+3. Add the Action as a modifier to a view
+ ```kotlin
+ Text(
+ title,
+ style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold),
+ modifier = GlanceModifier.clickable(onClick = actionRunCallback()),
+ )
+ ```
+
+## Example Blog Article
+
+A more in depth guide on how to build Interactive Widgets can be found [here](https://medium.com/@ABausG/83cb0706a417)
diff --git a/docs/features/ios-lock-screen.mdx b/docs/features/ios-lock-screen.mdx
new file mode 100644
index 00000000..84003d2d
--- /dev/null
+++ b/docs/features/ios-lock-screen.mdx
@@ -0,0 +1,54 @@
+---
+title: iOS Lock Screen Widgets
+description: Create Widgets for the iOS Lock Screen
+---
+
+# iOS Lock Screen Widgets
+
+On iOS, Lock Screen Widgets are a great way to show information to your users without them having to unlock their device.
+
+
+
+
+
+
+
+## Supported Families
+Technically, this works by adding another `family` to your widget's configuration.
+
+```swift
+var body: some WidgetConfiguration {
+ ...
+ .description("Lock Screen Widgets")
+ .supportedFamilies([
+ .systemSmall,
+ .systemMedium,
+ // Accessory Widgets are available on the Lock Screen only
+ .accessoryCircular
+ ])
+```
+
+## Adjust Layout
+
+To adjust the layout of the widget, you can check which family/size is currently requested in the widget's layout code.
+
+```swift
+struct LockScreenWidgetView: View {
+ var entry: Provider.Entry
+
+ // Detect the current Family
+ @Environment(\.widgetFamily) var family
+
+ var body: some View {
+ if family == .accessoryCircular {
+ // Return Widget for Circular Lock Screen Widget
+ } else {
+ // Build Widget for other families
+ }
+ }
+}
+```
+
+For more information on accessory widgets, check out the [official Apple Documentation](https://developer.apple.com/design/human-interface-guidelines/widgets#Interface-design).
+
+A full guide on how to add a Lock Screen Widget using home_widget can also be found in this [article](https://medium.com/@ABausG/ios-lockscreen-widgets-with-flutter-and-home-widget-0dfecc18cfa0).
\ No newline at end of file
diff --git a/docs/features/pin-widget.mdx b/docs/features/pin-widget.mdx
new file mode 100644
index 00000000..e3e329f8
--- /dev/null
+++ b/docs/features/pin-widget.mdx
@@ -0,0 +1,29 @@
+---
+title: Pin Widgets (Android)
+description: Use Pin Widget to add widgets directly to the HomeScreen on Android.
+---
+
+# Pin Widgets
+
+On Android, you can initiate an action to pin a widget to the HomeScreen, allowing your users to directly add your widget to their HomeScreen.
+
+
+
+
+
+
+
+```dart
+HomeWidget.requestPinWidget(
+ name: 'HomeWidgetExampleProvider',
+ androidName: 'HomeWidgetExampleProvider',
+ qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
+);
+```
+
+This method is only supported on [Android API 26+](https://developer.android.com/develop/ui/views/appwidgets/configuration#pin).
+If you want to check whether it is supported on the current device, use:
+
+```dart
+HomeWidget.isRequestPinWidgetSupported();
+```
\ No newline at end of file
diff --git a/docs/features/render-flutter-widgets.mdx b/docs/features/render-flutter-widgets.mdx
new file mode 100644
index 00000000..8bbf21c3
--- /dev/null
+++ b/docs/features/render-flutter-widgets.mdx
@@ -0,0 +1,114 @@
+---
+title: Render Flutter Widgets
+description: User renderFlutterWidget to render Flutter Widgets to the Native Widgets
+---
+
+# Render Flutter Widgets
+
+In some cases, you may not want to rewrite UI code in the native frameworks for your widgets. This works by generating a png file of the Flutter widget and save it to a shared container between your Flutter app and the home screen widget.
+
+
+
+
+Due to a limitation in `dart:ui` this method does not work when the App is fully in the background. In those cases you should try to build the UI natively.
+
+
+For example, say you have a chart in your Flutter app configured with `CustomPaint`:
+
+```dart
+class LineChart extends StatelessWidget {
+ const LineChart({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return CustomPaint(
+ painter: LineChartPainter(),
+ child: const SizedBox(
+ height: 200,
+ width: 200,
+ ),
+ );
+ }
+}
+```
+
+
+
+## Flutter
+
+To render a Flutter widget to an image, use the `renderFlutterWidget` method:
+
+```dart
+var path = await HomeWidget.renderFlutterWidget(
+ const LineChart(),
+ key: 'lineChart',
+ logicalSize: const Size(400, 400),
+);
+```
+- `LineChart()` is the widget that will be rendered as an image.
+- `key` is the key in the key/value storage on the device that stores the path of the file for easy retrieval on the native side
+
+## iOS
+
+To retrieve the image and display it in a widget, you can use the following SwiftUI code:
+
+1. In your `TimelineEntry` struct add a property to retrieve the path:
+ ```swift
+ struct MyEntry: TimelineEntry {
+ …
+ let lineChartPath: String
+ }
+ ```
+
+2. Get the path from the `UserDefaults` in `getSnapshot`:
+ ```swift
+ func getSnapshot(
+ ...
+ let lineChartPath = userDefaults?.string(forKey: "lineChart") ?? "No screenshot available"
+ ```
+3. Create a `View` to display the chart and resize the image based on the `displaySize` of the widget:
+ ```swift
+ struct WidgetEntryView : View {
+ …
+ var ChartImage: some View {
+ if let uiImage = UIImage(contentsOfFile: entry.lineChartPath) {
+ let image = Image(uiImage: uiImage)
+ .resizable()
+ .frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center)
+ return AnyView(image)
+ }
+ print("The image file could not be loaded")
+ return AnyView(EmptyView())
+ }
+ …
+ }
+ ```
+
+4. Display the chart in the body of the widget's `View`:
+ ```swift
+ VStack {
+ Text(entry.title)
+ Text(entry.description)
+ ChartImage
+ }
+ ```
+
+## Android
+
+On Android use the following code in your Glance Widget to display the Screenshot of the Flutter Widget
+
+```kotlin
+// Access data
+val data = currentState.preferences
+
+// Get Path
+val imagePath = data.getString("lineChart", null)
+
+// Add Image to Compose Tree
+imagePath?.let {
+ val bitmap = BitmapFactory.decodeFile(it)
+ Image(androidx.glance.ImageProvider(bitmap), null)
+}
+```
\ No newline at end of file
diff --git a/docs/index.mdx b/docs/index.mdx
new file mode 100644
index 00000000..5e540df2
--- /dev/null
+++ b/docs/index.mdx
@@ -0,0 +1,63 @@
+---
+title: home_widget Docs
+description: Documentation for the home_widget Flutter package.
+next: /usage/sync-data
+---
+
+# home_widget
+
+HomeWidget is a plugin to make it easier to create HomeScreen widgets on Android and iOS.
+HomeWidget does **not** allow writing widgets with Flutter itself. It still requires writing the widgets with native code. However, it provides a unified interface for sending data, retrieving data, and updating the widgets.
+
+
+
+| iOS | Android |
+|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
+|
|
|
+
+## Getting Started
+
+To get started with home_widget, take a look at the following documentation pages:
+
+- [Usage](/usage/sync-data) - How to use home_widget in Flutter. This includes how to send data to the Widget, how to update the widget, and how to read data from the widget
+- [iOS Setup](/setup/ios) - Set up home_widget on iOS for your Flutter App
+- [Android Setup](/setup/android) - Set up home_widget on Android for your Flutter App
+- Browse the various Feature entries to learn more about the capabilities of home_widget
+
+## Contributing
+
+Contributions are welcome!
+Here is how you can help:
+- Report bugs and request features via [GitHub Issues](https://github.com/ABausG/home_widget/issues)
+- Engage in discussions and help users solve their problems/questions in the [Discussions](https://github.com/ABausG/home_widget/discussions)
+- Fix typos and grammar mistakes
+- Update the documentation
+- Implement new features by making a pull-request
+
+
+## Show your Widgets
+
+Have you added HomeScreen Widgets to your App? Feel free to share them in the [GitHub Discussions](https://github.com/ABausG/home_widget/discussions/categories/show-and-tell)
+
+
+## Sponsors
+
+I develop this package in my free time. If you or your company benefits from home_widget, it would mean a lot to me if you considered supporting me on [GitHub Sponsors](https://github.com/sponsors/abausg).
+
+
+
+
+
+
+## Resources, Articles, Talks
+- [Google Codelab](https://codelabs.developers.google.com/flutter-home-screen-widgets#0)
+- [Interactive HomeScreen Widgets with Flutter using home_widget](https://medium.com/p/83cb0706a417)
+- [iOS Lock Screen Widgets with Flutter and home_widget](https://medium.com/p/0dfecc18cfa0)
diff --git a/docs/setup/android.mdx b/docs/setup/android.mdx
new file mode 100644
index 00000000..9287df93
--- /dev/null
+++ b/docs/setup/android.mdx
@@ -0,0 +1,127 @@
+---
+title: Android Setup
+description: Setup home_widget on Android for your Flutter App using Jetpack Glance
+previousTitle: iOS Setup
+previous: /setup/ios
+---
+
+
+# Android Setup
+
+Learn how to setup home_widget on Android for your Flutter App using Jetpack Glance.
+
+
+If you are looking for support for Android XML Widgets, please refer to the [Android XML](/android-xml/overview) section.
+
+
+## Dependencies
+
+### Jetpack Glance
+
+Add Jetpack Glance as a dependency to you app's Gradle File
+```groovy
+implementation 'androidx.glance:glance-appwidget:LATEST-VERSION'
+```
+
+### Compose Support
+Enable Compose Support in your apps `build.gradle`
+```groovy
+android {
+ ...
+ buildFeatures {
+ compose true
+ }
+}
+```
+
+## Necessary Files
+
+For the correct setup of HomeScreenWidgets you need to create a series of files.
+
+### Widget Configuration
+In `android/app/src/main/res/xml` you need to create a configuration file.
+In here you can configure properties used for things like size constraints and preview layouts.
+```xml
+
+
+```
+For more Information on the possible contents of this File check the official Android Documentation [here](https://developer.android.com/develop/ui/views/appwidgets#AppWidgetProviderInfo)
+
+### AppWidget
+
+The `GlanceAppWidget` is the file in which you define your Widget's layout. Should look something like this
+
+```kotlin
+// Other imports...
+import HomeWidgetGlanceState
+import HomeWidgetGlanceStateDefinition
+
+class AppWidget : GlanceAppWidget() {
+
+ override val stateDefinition: GlanceStateDefinition<*>?
+ get() = HomeWidgetGlanceStateDefinition()
+
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+ provideContent {
+ GlanceContent(context, currentState())
+ }
+ }
+
+ @Composable
+ private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
+ val prefs = currentState.preferences
+ val counter = prefs.getInt("counter", 0)
+ Box(modifier = GlanceModifier.background(Color.White).padding(16.dp)) {
+ Column() {
+ Text(
+ counter.toString()
+ )
+ }
+ }
+ }
+}
+```
+
+Note the override for the `stateDefinition` this is what enables home_widget to update the Widget.
+```kotlin
+override val stateDefinition: GlanceStateDefinition<*>?
+ get() = HomeWidgetGlanceStateDefinition()
+```
+
+### WidgetReceiver
+
+To get automatic Updates you should extend from [HomeWidgetGlanceWidgetReceiver](android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt)
+
+Your Receiver should then look like this, using the previously define `AppWidget` as the generic type.
+
+```kotlin
+// Remember to set a package in order for home_widget to find the Receiver
+package es.antonborri.home_widget_example.glance
+
+import HomeWidgetGlanceWidgetReceiver
+
+class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() {
+ override val glanceAppWidget = HomeWidgetGlanceAppWidget()
+}
+```
+
+
+### Register Widget in AndroidManifest.xml
+
+Tie everything together by registering the Widget in your `AndroidManifest.xml`
+```xml
+
+
+
+
+
+
+```
diff --git a/docs/setup/ios.mdx b/docs/setup/ios.mdx
new file mode 100644
index 00000000..0736e935
--- /dev/null
+++ b/docs/setup/ios.mdx
@@ -0,0 +1,130 @@
+---
+title: iOS Setup
+description: Setup home_widget on iOS for your Flutter App
+nextTitle: Android Setup
+next: /setup/android
+---
+
+# iOS Setup
+
+On iOS Widgets are an App Extension.
+The following steps explain how to add the correct App Extension, how to do the basic configuration to read/display data you send from your App in the Widget and how to setup GroupIds to ensure the correct communication between your App and the Widget.
+
+
+## Add a Widget to your App in Xcode
+Add a widget extension by going File > New > Target > Widget Extension
+
+
+
+The generated Widget code includes the following classes:
+- `TimelineProvider` - Provides a Timeline of entries at which the System will update the Widget automatically
+- `TimelineEntry` - Represents the Data Object used to build the Widget. The `data` 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
+
+## Configure Widget
+
+### Widget
+In the Widget Configuration it is important to set the `kind` to the same value as the `name`/`iOSName` in the `updateWidget` function in Flutter
+
+### TimelineEntry / TimelineProvider
+Adjust the `TimelineEntry` to match your desired Data Structure you need to build your Widget. This entry is what is passed to the actual `View` to build the Widget.TimelineEntry
+
+```swift
+struct CounterEntry: TimelineEntry {
+ let date: Date
+ let counter: Int
+}
+```
+
+Adjust the `TimelineProvider` to build a timeline (can be single entry if all updating is always handled from Flutter) where you create a `TimelineEntry` based on the Data stored with `HomeWidget.saveWidgetData`
+```swift
+struct Provider: TimelineProvider {
+ func placeholder(in context: Context) -> SimpleEntry {
+ SimpleEntry(date: Date(), counter: 0)
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
+ let prefs = UserDefaults(suiteName: "group.YOUR_GROUP_ID")
+ let counter = prefs?.integer(forKey: "counter")
+ let entry = CounterEntry(date: Date(), counter: counter ?? 0)
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
+ getSnapshot(in: context) { (entry) in
+ let timeline = Timeline(entries: [entry], policy: .atEnd)
+ completion(timeline)
+ }
+ }
+}
+```
+
+
+### View
+In the View you build your Layout of the Widget. Using the `TimelineEntry` you can access the Data you stored in `UserDefaults` and build your Widget accordingly
+
+```swift
+struct WidgetEntryView : View {
+ var entry: Provider.Entry
+
+ var body: some View {
+ VStack {
+ Text(entry.counter.description)
+ }
+ }
+}
+```
+
+For more Information on how to build Widgets in iOS, check out the [Apple Documentation](https://developer.apple.com/documentation/swiftui/widget)
+
+## GroupId
+home_widget syncs data between your App and the Widget using App Groups.
+
+**Note: in order to add groupIds you need a paid Apple Developer Account**
+
+Go to your [Apple Developer Account](https://developer.apple.com/account/resources/identifiers/list/applicationGroup) and add a new group.
+Add this group to your Runner and the Widget Extension inside XCode: Signing & Capabilities > App Groups > +.
+
+To swap between your App, and the Extension change the Target)
+
+
+### Setup in Flutter
+
+For iOS, you need to call `HomeWidget.setAppGroupId('YOUR_GROUP_ID');`
+Without this you won't be able to share data between your App and the Widget and calls to `saveWidgetData` and `getWidgetData` will return an error
+
+## Sync CFBundleVersion (optional)
+This step is optional, this will sync the widget extension build version with your app version, so you don't get warnings of mismatch version from App Store Connect when uploading your app.
+
+
+
+In your Runner (app) target go to Build Phases > + > New Run Script Phase and add the following script:
+```bash
+generatedPath="$SRCROOT/Flutter/Generated.xcconfig"
+
+# Read and trim versionNumber and buildNumber
+versionNumber=$(grep FLUTTER_BUILD_NAME "$generatedPath" | cut -d '=' -f2 | xargs)
+buildNumber=$(grep FLUTTER_BUILD_NUMBER "$generatedPath" | cut -d '=' -f2 | xargs)
+
+infoPlistPath="$SRCROOT/HomeExampleWidget/Info.plist"
+
+# Check and add CFBundleVersion if it does not exist
+/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$infoPlistPath" 2>/dev/null
+if [ $? != 0 ]; then
+ /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $buildNumber" "$infoPlistPath"
+else
+ /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$infoPlistPath"
+fi
+
+# Check and add CFBundleShortVersionString if it does not exist
+/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$infoPlistPath" 2>/dev/null
+if [ $? != 0 ]; then
+ /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $versionNumber" "$infoPlistPath"
+else
+ /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $versionNumber" "$infoPlistPath"
+fi
+
+```
+
+Replace `HomeExampleWidget` with the name of the widget extension folder that you have created.
diff --git a/docs/usage/sync-data.mdx b/docs/usage/sync-data.mdx
new file mode 100644
index 00000000..8e09eaf7
--- /dev/null
+++ b/docs/usage/sync-data.mdx
@@ -0,0 +1,27 @@
+---
+title: Read and write Data
+description: Read and write Data
+---
+
+# Read and Write Data
+
+By default home_widget uses UserDefaults on iOS and SharedPreferences on Android to store data.
+In this section it is explained how to save and read data from the Flutter App as well as how to access data from your HomeScreen Widgets
+
+## Save Data
+
+In order to save Data call
+```dart
+HomeWidget.saveWidgetData('id', data);
+```
+
+## Read Data
+
+To retrieve the current Data saved in the Widget call
+```dart
+HomeWidget.getWidgetData('id', defaultValue: data);
+```
+
+Check out the platform documentation of
+[iOS](/setup/ios#timelineentry--timelineprovider) and [Android](/setup/android#appwidget)
+to see how to access this data in your widget.
diff --git a/docs/usage/update-widget.mdx b/docs/usage/update-widget.mdx
new file mode 100644
index 00000000..7fcedc2e
--- /dev/null
+++ b/docs/usage/update-widget.mdx
@@ -0,0 +1,36 @@
+---
+title: Update Widget
+description: How to Update a native Widget from Flutter
+nextTitle: iOS Setup
+next: /setup/android
+---
+
+# Update Widget
+
+In order to initiate a reload of the HomeScreenWidget you need to call
+```dart
+HomeWidget.updateWidget(
+ name: 'HomeWidgetExampleProvider',
+ androidName: 'HomeWidgetExampleProvider',
+ iOSName: 'HomeWidgetExample',
+ qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
+);
+```
+
+
+Not all the arguments are required. Depending on your setup you might need to call different arguments of the function.
+For iOS either `name` or `iOSName` must match the `kind` that is defined for the Widget
+For Android either `name` or `androidName` must the class Name of your Widget Receiver. Alternatively you can point to the receivers full class using `qualifiedAndroidName`
+
+
+
+#### Android Glance
+
+To ensure your Android Glance Widget is getting updated you need to add the following snippet to your Android Glance Widget. Please also make sure you read the full documentation on [Android Glance](/setup/android) to ensure you have the correct setup.
+
+```kotlin
+override val stateDefinition: GlanceStateDefinition<*>?
+ get() = HomeWidgetGlanceStateDefinition()
+```
+
+
diff --git a/packages/home_widget/CHANGELOG.md b/packages/home_widget/CHANGELOG.md
index 0801cbd6..35931ef0 100644
--- a/packages/home_widget/CHANGELOG.md
+++ b/packages/home_widget/CHANGELOG.md
@@ -66,7 +66,7 @@ stepushchik-denis-gismart](https://github.com/stepushchik-denis-gismart)
* Fix collision for Deeplinks [#42](https://github.com/ABausG/home_widget/pull/42) by [mgonzalezc](https://github.com/mgonzalezc)
* Make Android PendingIntents immutable for Android 12 [#49](https://github.com/ABausG/home_widget/pull/49) by [mgonzalezc](https://github.com/mgonzalezc)
* Update Gradle Versions and target Android SDK 31
-* Fix Issues rrelating to `initiallyLaunchedFromHomeWidget`
+* Fix Issues relating to `initiallyLaunchedFromHomeWidget`
* [#48](https://github.com/ABausG/home_widget/issues/48) Call not completing on iOS
* [#40](https://github.com/ABausG/home_widget/issues/40) Cast exception on Android for cases launched from Widget but without data Uri
diff --git a/packages/home_widget/README.md b/packages/home_widget/README.md
index c325b116..5e212b1e 100644
--- a/packages/home_widget/README.md
+++ b/packages/home_widget/README.md
@@ -6,670 +6,62 @@
[](https://pub.dev/packages/home_widget/score)
[](https://github.com/ABausG/home_widget/actions/workflows/main.yml?query=branch%3Amain)
[](https://codecov.io/gh/ABausG/home_widget)
+[](https://github.com/sponsors/abausg)
-HomeWidget is a Plugin to make it easier to create HomeScreen Widgets on Android and iOS.
-HomeWidget does **not** allow writing Widgets with Flutter itself. It still requires writing the Widgets with native code. However, it provides a unified Interface for sending data, retrieving data and updating the Widgets
+HomeWidget is a plugin to make it easier to create HomeScreen Widgets on Android and iOS.
+HomeWidget does **not** allow writing Widgets with Flutter itself. It still requires writing the Widgets with native code. However, it provides a unified interface for sending data, retrieving data, and updating the Widgets.
| iOS | Android |
|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
-|
|
|
+|
|
|
-## Platform Setup
-In order to work correctly there needs to be some platform specific setup. Check below on how to add support for Android and iOS
+## Features
+- Help setting up widgets on the native side
+- Send data from Flutter to the HomeScreen widget and update them
+- Render and display Flutter widgets on HomeScreen widgets
+- Interactive widgets that call Dart Code
-iOS
-
-### Add a Widget to your App in Xcode
-Add a widget extension by going File > New > Target > Widget Extension
-
-
-
-
-### Add GroupId
-You need to add a groupId to the App and the Widget Extension
-
-**Note: in order to add groupIds you need a paid Apple Developer Account**
-
-Go to your [Apple Developer Account](https://developer.apple.com/account/resources/identifiers/list/applicationGroup) and add a new group.
-Add this group to your Runner and the Widget Extension inside XCode: Signing & Capabilities > App Groups > +.
-(To swap between your App, and the Extension change the Target)
-
-
-
-### Sync CFBundleVersion (optional)
-This step is optional, this will sync the widget extension build version with your app version, so you don't get warnings of mismatch version from App Store Connect when uploading your app.
-
-
-
-In your Runner (app) target go to Build Phases > + > New Run Script Phase and add the following script:
-```bash
-generatedPath="$SRCROOT/Flutter/Generated.xcconfig"
-
-# Read and trim versionNumber and buildNumber
-versionNumber=$(grep FLUTTER_BUILD_NAME "$generatedPath" | cut -d '=' -f2 | xargs)
-buildNumber=$(grep FLUTTER_BUILD_NUMBER "$generatedPath" | cut -d '=' -f2 | xargs)
-
-infoPlistPath="$SRCROOT/HomeExampleWidget/Info.plist"
-
-# Check and add CFBundleVersion if it does not exist
-/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$infoPlistPath" 2>/dev/null
-if [ $? != 0 ]; then
- /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $buildNumber" "$infoPlistPath"
-else
- /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$infoPlistPath"
-fi
-
-# Check and add CFBundleShortVersionString if it does not exist
-/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$infoPlistPath" 2>/dev/null
-if [ $? != 0 ]; then
- /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $versionNumber" "$infoPlistPath"
-else
- /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $versionNumber" "$infoPlistPath"
-fi
-
-```
-
-Replace `HomeExampleWidget` with the name of the widget extension folder that you have created.
-
-
-### Write your Widget
-Check the [Example App](example/ios/HomeWidgetExample/HomeWidgetExample.swift) for an Implementation of a Widget.
-A more detailed overview on how to write Widgets for iOS 14 can be found on the [Apple Developer documentation](https://developer.apple.com/documentation/swiftui/widget).
-In order to access the Data send with Flutter can be access with
-```swift
-let data = UserDefaults.init(suiteName:"YOUR_GROUP_ID")
-```
-
-
-Android (Jetpack Glance)
-
-### Add Jetpack Glance as a dependency to you app's Gradle File
-```groovy
-implementation 'androidx.glance:glance-appwidget:LATEST-VERSION'
-```
-
-### Create Widget Configuration into `android/app/src/main/res/xml`
-```xml
-
-
-```
-
-### Add WidgetReceiver to AndroidManifest
-```xml
-
-
-
-
-
-
-```
-
-### Create WidgetReceiver
-
-To get automatic Updates you should extend from [HomeWidgetGlanceWidgetReceiver](android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetGlanceWidgetReceiver.kt)
-
-Your Receiver should then look like this
-
-```kotlin
-package es.antonborri.home_widget_example.glance
-
-import HomeWidgetGlanceWidgetReceiver
-
-class HomeWidgetReceiver : HomeWidgetGlanceWidgetReceiver() {
- override val glanceAppWidget = HomeWidgetGlanceAppWidget()
-}
-```
-
-### Build Your AppWidget
-
-```kotlin
-
-class HomeWidgetGlanceAppWidget : GlanceAppWidget() {
-
- /**
- * Needed for Updating
- */
- override val stateDefinition = HomeWidgetGlanceStateDefinition()
-
- override suspend fun provideGlance(context: Context, id: GlanceId) {
- provideContent {
- GlanceContent(context, currentState())
- }
- }
-
- @Composable
- private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
- // Use data to access the data you save with
- val data = currentState.preferences
-
-
- // Build Your Composable Widget
- Column(
- ...
- }
-
-```
-
-
-
-Android (XML)
-
-### Create Widget Layout inside `android/app/src/main/res/layout`
-
-### Create Widget Configuration into `android/app/src/main/res/xml`
-```xml
-
-
-
-```
-
-### Add WidgetReceiver to AndroidManifest
-```xml
-
-
-
-
-
-
-```
-
-### Write your WidgetProvider
-For convenience, you can extend from [HomeWidgetProvider](android/src/main/kotlin/es/antonborri/home_widget/HomeWidgetProvider.kt) which gives you access to a SharedPreferences Object with the Data in the `onUpdate` method.
-In case you don't want to use the convenience Method you can access the Data using
-```kotlin
-import es.antonborri.home_widget.HomeWidgetPlugin
-...
-HomeWidgetPlugin.getData(context)
-```
-which will give you access to the same SharedPreferences
-
-### More Information
-For more Information on how to create and configure Android Widgets, check out [this guide](https://developer.android.com/develop/ui/views/appwidgets) on the Android Developers Page.
-
-
+## Documentation
+Check out the [documentation](https://docs.page/abausg/home_widget) to learn how to setup home_widget and HomeScreen Widgets on your desired Platforms.
## Usage
-
-### Setup
-iOS
-
-For iOS, you need to call `HomeWidget.setAppGroupId('YOUR_GROUP_ID');`
-Without this you won't be able to share data between your App and the Widget and calls to `saveWidgetData` and `getWidgetData` will return an error
-
-
+Once you wrote your Widgets on the native side, it is super easy to send data to the Widget and update it.
### Save Data
-In order to save Data call `HomeWidget.saveWidgetData('id', data)`
-### Update a Widget
-In order to force a reload of the HomeScreenWidget you need to call
+To save data, call:
```dart
-HomeWidget.updateWidget(
- name: 'HomeWidgetExampleProvider',
- androidName: 'HomeWidgetExampleProvider',
- iOSName: 'HomeWidgetExample',
- qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
-);
+HomeWidget.saveWidgetData('id', data);
```
-The name for Android will be chosen by checking `qualifiedAndroidName`, falling back to `.androidName` and if that was not provided it will fallback to `.name`.
-This Name needs to be equal to the Classname of the [WidgetProvider](#Write-your-Widget)
-
-The name for iOS will be chosen by checking `iOSName` if that was not provided it will fallback to `name`.
-This name needs to be equal to the Kind specified in you Widget
-
-#### Android (Jetpack Glance)
-
-If you followed the guide and use `HomeWidgetGlanceWidgetReceiver` as your Receiver, `HomeWidgetGlanceStateDefinition` as the AppWidgetStateDefinition, `currentState()` in the composable view and `currentState.preferences` for data access. No further work is necessary.
-
-#### Android (XML)
-Calling `HomeWidget.updateWidget` only notifies the specified provider.
-To update widgets using this provider,
-update them from the provider like this:
-
-```kotlin
-class HomeWidgetExampleProvider : HomeWidgetProvider() {
-
- override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
- appWidgetIds.forEach { widgetId ->
- val views = RemoteViews(context.packageName, R.layout.example_layout).apply {
- // ...
- }
-
- // Update widget.
- appWidgetManager.updateAppWidget(widgetId, views)
- }
- }
-}
-```
-
-
-### Retrieve Data
-To retrieve the current Data saved in the Widget call `HomeWidget.getWidgetData('id', defaultValue: data)`
-
-### Interactive Widgets
-
-Android and iOS (starting with iOS 17) allow widgets to have interactive Elements like Buttons
-
-Dart
-
-1. Write a **static** function that takes a Uri as an argument. This will get called when a user clicks on the View
- ```dart
- @pragma("vm:entry-point")
- FutureOr backgroundCallback(Uri data) async {
- // do something with data
- ...
- }
- ```
- `@pragma('vm:entry-point')` must be placed above the `callback` function to avoid tree shaking in release mode.
-
-2. Register the callback function by calling
- ```dart
- HomeWidget.registerInteractivityCallback(backgroundCallback);
- ```
-
-
-iOS
-
-1. Adjust your Podfile to add `home_widget` as a dependency to your WidgetExtension
- ```rb
- target 'YourWidgetExtension' do
- use_frameworks!
- use_modular_headers!
-
- pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
- end
- ```
-2. To be able to use plugins with the Background Callback add this to your AppDelegate's `application` function
- ```swift
- if #available(iOS 17, *) {
- HomeWidgetBackgroundWorker.setPluginRegistrantCallback { registry in
- GeneratedPluginRegistrant.register(with: registry)
- }
- }
- ```
-3. Create a custom `AppIntent` in your App Target (Runner) and make sure to select both your App and your WidgetExtension in the Target Membership panel
-
- 
-
- In this Intent you should import `home_widget` and call `HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)` in the perform method. `url` and `appGroup` can be either hardcoded or set as parameters from the Widget
- ```swift
- import AppIntents
- import Flutter
- import Foundation
- import home_widget
-
- @available(iOS 16, *)
- public struct BackgroundIntent: AppIntent {
- static public var title: LocalizedStringResource = "HomeWidget Background Intent"
-
- @Parameter(title: "Widget URI")
- var url: URL?
-
- @Parameter(title: "AppGroup")
- var appGroup: String?
-
- public init() {}
-
- public init(url: URL?, appGroup: String?) {
- self.url = url
- self.appGroup = appGroup
- }
-
- public func perform() async throws -> some IntentResult {
- await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
-
- return .result()
- }
- }
- ```
-4. Add a Button to your Widget. This Button might be encapsulated by a Version check. Pass in an instance of the `AppIntent` created in the previous step
- ```swift
- Button(
- intent: BackgroundIntent(
- url: URL(string: "homeWidgetExample://titleClicked"), appGroup: widgetGroupId)
- ) {
- Text(entry.title).bold().font( /*@START_MENU_TOKEN@*/.title /*@END_MENU_TOKEN@*/)
- }.buttonStyle(.plain)
- ```
-5. With the current setup the Widget is now Interactive as long as the App is still in the background. If you want to have the Widget be able to wake the App up you need to add the following to your `AppIntent` file
- ```swift
- @available(iOS 16, *)
- @available(iOSApplicationExtension, unavailable)
- extension BackgroundIntent: ForegroundContinuableIntent {}
- ```
- This code tells the system to always perform the Intent in the App and not in a process attached to the Widget. Note however that this will start your Flutter App using the normal main entrypoint meaning your full app might be run in the background. To counter this you should add checks in the very first Widget you build inside `runApp` to only perform necessary calls/setups while the App is launched in the background
-
-
-
-Android Jetpack Glance
-
-1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file
- ```
-
-
-
-
-
-
- ```
-2. Create a custom Action
- ```kotlin
- class InteractiveAction : ActionCallback {
- override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
- val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("homeWidgetExample://titleClicked"))
- backgroundIntent.send()
- }
- }
- ```
-3. Add the Action as a modifier to a view
- ```kotlin
- Text(
- title,
- style = TextStyle(fontSize = 36.sp, fontWeight = FontWeight.Bold),
- modifier = GlanceModifier.clickable(onClick = actionRunCallback()),
- )
- ```
-
-
-
-Android XML
-
-1. Add the necessary Receiver and Service to your `AndroidManifest.xml` file
- ```
-
-
-
-
-
-
- ```
-2. Add a `HomeWidgetBackgroundIntent.getBroadcast` PendingIntent to the View you want to add a click listener to
- ```kotlin
- val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
- context,
- Uri.parse("homeWidgetExample://titleClicked")
- )
- setOnClickPendingIntent(R.id.widget_title, backgroundIntent)
- ```
-
-
-### Using images of Flutter widgets
-
-In some cases, you may not want to rewrite UI code in the native frameworks for your widgets.
-
-Dart
-For example, say you have a chart in your Flutter app configured with `CustomPaint`:
+### Update Widget
+To initiate a reload of the Home Screen Widget, you need to call:
```dart
-class LineChart extends StatelessWidget {
- const LineChart({
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- return CustomPaint(
- painter: LineChartPainter(),
- child: const SizedBox(
- height: 200,
- width: 200,
- ),
- );
- }
-}
-```
-
-
-
-Rewriting the code to create this chart on both Android and iOS might be time consuming.
-Instead, you can generate a png file of the Flutter widget and save it to a shared container
-between your Flutter app and the home screen widget.
-
-```dart
-var path = await HomeWidget.renderFlutterWidget(
- const LineChart(),
- key: 'lineChart',
- logicalSize: const Size(400, 400),
+HomeWidget.updateWidget(
+ name: 'HomeWidgetExampleProvider',
);
```
-- `LineChart()` is the widget that will be rendered as an image.
-- `key` is the key in the key/value storage on the device that stores the path of the file for easy retrieval on the native side
-
-
-iOS
-To retrieve the image and display it in a widget, you can use the following SwiftUI code:
-
-1. In your `TimelineEntry` struct add a property to retrieve the path:
- ```swift
- struct MyEntry: TimelineEntry {
- …
- let lineChartPath: String
- }
- ```
-
-2. Get the path from the `UserDefaults` in `getSnapshot`:
- ```swift
- func getSnapshot(
- ...
- let lineChartPath = userDefaults?.string(forKey: "lineChart") ?? "No screenshot available"
- ```
-3. Create a `View` to display the chart and resize the image based on the `displaySize` of the widget:
- ```swift
- struct WidgetEntryView : View {
- …
- var ChartImage: some View {
- if let uiImage = UIImage(contentsOfFile: entry.lineChartPath) {
- let image = Image(uiImage: uiImage)
- .resizable()
- .frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center)
- return AnyView(image)
- }
- print("The image file could not be loaded")
- return AnyView(EmptyView())
- }
- …
- }
- ```
-
-4. Display the chart in the body of the widget's `View`:
- ```swift
- VStack {
- Text(entry.title)
- Text(entry.description)
- ChartImage
- }
- ```
-
-
-
-
-Android (Jetpack Glance)
-
-```kotlin
-// Access data
-val data = currentState.preferences
-
-// Get Path
-val imagePath = data.getString("lineChart", null)
-
-// Add Image to Compose Tree
-imagePath?.let {
- val bitmap = BitmapFactory.decodeFile(it)
- Image(androidx.glance.ImageProvider(bitmap), null)
-}
-```
-
-
-
-Android (XML)
-
-1. Add an image UI element to your xml file:
- ```xml
-
- ```
-2. Update your Kotlin code to get the chart image and put it into the widget, if it exists.
- ```kotlin
- class NewsWidget : AppWidgetProvider() {
- override fun onUpdate(
- context: Context,
- appWidgetManager: AppWidgetManager,
- appWidgetIds: IntArray,
- ) {
- for (appWidgetId in appWidgetIds) {
- // Get reference to SharedPreferences
- val widgetData = HomeWidgetPlugin.getData(context)
- val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
- // Get chart image and put it in the widget, if it exists
- val imagePath = widgetData.getString("lineChart", null)
- val imageFile = File(imagePath)
- val imageExists = imageFile.exists()
- if (imageExists) {
- val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
- setImageViewBitmap(R.id.widget_image, myBitmap)
- } else {
- println("image not found!, looked @: $imagePath")
- }
- // End new code
- }
- appWidgetManager.updateAppWidget(appWidgetId, views)
- }
- }
- }
- ```
-
-
-### Launch App and Detect which Widget was clicked
-To detect if the App has been initially started by clicking the Widget you can call `HomeWidget.initiallyLaunchedFromHomeWidget()` if the App was already running in the Background you can receive these Events by listening to `HomeWidget.widgetClicked`. Both methods will provide Uris, so you can easily send back data from the Widget to the App to for example navigate to a content page.
-
-In order for these methods to work you need to follow these steps:
-
-iOS
-
-Add `.widgetUrl` to your WidgetComponent
-```swift
-Text(entry.message)
- .font(.body)
- .widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget"))
-```
-In order to only detect Widget Links you need to add the queryParameter`homeWidget` to the URL
-
-
-Android Jetpack Glance
-
-Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest`
-```
-
-
-
-```
-
-Add the following modifier to your Widget (import from HomeWidget)
-```kotlin
-Text(
- message,
- style = TextStyle(fontSize = 18.sp),
- modifier = GlanceModifier.clickable(
- onClick = actionStartActivity(
- context,
- Uri.parse("homeWidgetExample://message?message=$message")
- )
- )
-)
-```
-
+## Contributing
-Android XML
+Contributions are welcome!
+Here is how you can help:
+- Report bugs and request features via [GitHub Issues](https://github.com/ABausG/home_widget/issues)
+- Engage in discussions and help users solve their problems/questions in the [Discussions](https://github.com/ABausG/home_widget/discussions)
+- Fix typos and grammar mistakes
+- Update the documentation
+- Implement new features by making a pull-request
-Add an `IntentFilter` to the `Activity` Section in your `AndroidManifest`
-```
-
-
-
-```
-
-In your WidgetProvider add a PendingIntent to your View using `HomeWidgetLaunchIntent.getActivity`
-```kotlin
-val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
- context,
- MainActivity::class.java,
- Uri.parse("homeWidgetExample://message?message=$message"))
-setOnClickPendingIntent(R.id.widget_message, pendingIntentWithData)
-```
-
-
-
-### Background Update
-As the methods of HomeWidget are static it is possible to use HomeWidget in the background to update the Widget even when the App is in the background.
-
-The example App is using the [flutter_workmanager](https://pub.dev/packages/workmanager) plugin to achieve this.
-Please follow the Setup Instructions for flutter_workmanager (or your preferred background code execution plugin). Most notably make sure that Plugins get registered in iOS in order to be able to communicate with the HomeWidget Plugin.
-In case of flutter_workmanager this achieved by adding:
-```swift
-WorkmanagerPlugin.setPluginRegistrantCallback { registry in
- GeneratedPluginRegistrant.register(with: registry)
-}
-```
-to [AppDelegate.swift](example/ios/Runner/AppDelegate.swift)
-
-### Request Pin Widget
-Requests to Pin (Add) the Widget to the users HomeScreen by pinning it to the users HomeScreen.
+## Show your Widgets
-```dart
-HomeWidget.requestPinWidget(
- name: 'HomeWidgetExampleProvider',
- androidName: 'HomeWidgetExampleProvider',
- qualifiedAndroidName: 'com.example.app.HomeWidgetExampleProvider',
-);
-```
-
-This method is only supported on [Android, API 26+](https://developer.android.com/develop/ui/views/appwidgets/configuration#pin).
-If you want to check whether it is supported on current device, use:
-
-```dart
-HomeWidget.isRequestPinWidgetSupported();
-```
+Have you added HomeScreen widgets to your App? Feel free to share them in the [GitHub Discussions](https://github.com/ABausG/home_widget/discussions/categories/show-and-tell)
----
+## Sponsors
-## Resources, Articles, Talks
-Please add to this list if you have interesting and helpful resources
-- [Google Codelab](https://codelabs.developers.google.com/flutter-home-screen-widgets#0)
-- [Interactive HomeScreen Widgets with Flutter using home_widget](https://medium.com/p/83cb0706a417)
-- [iOS Lockscreen Widgets with Flutter and home_widget](https://medium.com/p/0dfecc18cfa0)
+I develop this package in my free time. If you or your company benefits from home_widget, it would mean a lot to me if you considered supporting me on [GitHub Sponsors](https://github.com/sponsors/abausg)
+
+
+
+
+
diff --git a/packages/home_widget/example/android/app/src/main/res/drawable/launch_background.xml b/packages/home_widget/example/android/app/src/main/res/drawable/launch_background.xml
index 304732f8..0ad9adaa 100644
--- a/packages/home_widget/example/android/app/src/main/res/drawable/launch_background.xml
+++ b/packages/home_widget/example/android/app/src/main/res/drawable/launch_background.xml
@@ -2,11 +2,4 @@
-
-
-
diff --git a/packages/home_widget/example/integration_test/ios_test.dart b/packages/home_widget/example/integration_test/ios_test.dart
index 8f125d23..91bea35c 100644
--- a/packages/home_widget/example/integration_test/ios_test.dart
+++ b/packages/home_widget/example/integration_test/ios_test.dart
@@ -32,7 +32,7 @@ void main() {
setUpAll(() async {
// Add Group Id
- await HomeWidget.setAppGroupId('group.es.antonborri.integrationtest');
+ await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
// Clear all Data
for (final key in testData.keys) {
await HomeWidget.saveWidgetData(key, null);
@@ -83,7 +83,7 @@ void main() {
testWidgets(
'Initially Launched completes and returns null if not launched from widget',
(tester) async {
- await HomeWidget.setAppGroupId('group.es.antonborri.integrationtest');
+ await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
final retrievedData =
await HomeWidget.initiallyLaunchedFromHomeWidget();
expect(retrievedData, isNull);
@@ -95,7 +95,7 @@ void main() {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
final hasInteractiveWidgets =
double.parse(deviceInfo.systemVersion.split('.').first) >= 17.0;
- await HomeWidget.setAppGroupId('group.es.antonborri.integrationtest');
+ await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
if (hasInteractiveWidgets) {
final registerCallbackResult =
await HomeWidget.registerInteractivityCallback(
diff --git a/packages/home_widget/ios/Classes/SwiftHomeWidgetPlugin.swift b/packages/home_widget/ios/Classes/SwiftHomeWidgetPlugin.swift
index 45465b99..3822161c 100644
--- a/packages/home_widget/ios/Classes/SwiftHomeWidgetPlugin.swift
+++ b/packages/home_widget/ios/Classes/SwiftHomeWidgetPlugin.swift
@@ -148,9 +148,9 @@ public class SwiftHomeWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandle
return
}
if #available(iOS 17.0, *) {
- let callbackHandels = call.arguments as! [Int64]
- let dispatcher = callbackHandels[0]
- let callback = callbackHandels[1]
+ let callbackHandles = call.arguments as! [Int64]
+ let dispatcher = callbackHandles[0]
+ let callback = callbackHandles[1]
let preferences = UserDefaults.init(suiteName: SwiftHomeWidgetPlugin.groupId)
preferences?.setValue(dispatcher, forKey: HomeWidgetBackgroundWorker.dispatcherKey)
preferences?.setValue(callback, forKey: HomeWidgetBackgroundWorker.callbackKey)
diff --git a/packages/home_widget/pubspec.yaml b/packages/home_widget/pubspec.yaml
index b98c6c97..839353fd 100644
--- a/packages/home_widget/pubspec.yaml
+++ b/packages/home_widget/pubspec.yaml
@@ -14,6 +14,8 @@ screenshots:
path: pub/screenshots/ios-counter.gif
- description: 'Android HomeScreen Widget'
path: pub/screenshots/android.png
+issue_tracker: https://github.com/ABausG/home_widget/issues
+documentation: https://docs.page/ABausG/home_widget
environment:
sdk: '>=3.4.0 <4.0.0'