Skip to content

feat: Add support for HomeWidget.saveFile and HomeWidget.saveImage#409

Merged
ABausG merged 13 commits into
mainfrom
feat/save-files
Apr 11, 2026
Merged

feat: Add support for HomeWidget.saveFile and HomeWidget.saveImage#409
ABausG merged 13 commits into
mainfrom
feat/save-files

Conversation

@ABausG

@ABausG ABausG commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Description

  • Add support for HomeWidget.saveFile and HomeWidget.saveImage
  • Also check for files when clearing data and delete them (can be turned off)

Checklist

  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation and added code (documentation) comments where necessary.
  • I have updated/added relevant examples in example or documentation.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

@docs-page

docs-page Bot commented Apr 9, 2026

Copy link
Copy Markdown

To view this pull requests documentation preview, visit the following URL:

docs.page/abausg/home_widget~409

Documentation is deployed and generated using docs.page.

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds file and image persistence to home_widget: new APIs saveFile/saveImage, optional deleteFile in saveWidgetData, renderFlutterWidget now persists PNGs via saveFile, plus docs, tests, and a full Android/iOS example demonstrating native widget consumption.

Changes

Cohort / File(s) Summary
Documentation
docs.json, docs/features/save-files.mdx, docs/features/render-flutter-widgets.mdx, docs/usage/sync-data.mdx
Added "Save files and images" page and sidebar entry; documented storage path format, key/extension validation, native retrieval, and that renderFlutterWidget saves PNG via same storage as saveFile.
Core Library API
packages/home_widget/lib/src/home_widget.dart
Added saveFile() and saveImage(); made saveWidgetData(..., {deleteFile = true}) async with optional file-deletion logic; added key/extension validation and refactored renderFlutterWidget to use saveFile.
Core Unit Tests
packages/home_widget/test/home_widget_test.dart, packages/home_widget/test/utils/test_png.dart
Added unit tests for delete-file semantics, saveFile/saveImage behavior, validation, path formatting, and a 1×1 PNG test-byte helper.
Integration Tests
packages/home_widget/example/integration_test/...
Per-test setup changes and integration tests covering JSON/PNG round-trips, saveImage decoding, on-disk verification, and deleteFile vs deleteFile: false semantics.
Example App — Flutter
examples/file_and_images/lib/main.dart, examples/file_and_images/pubspec.yaml, examples/file_and_images/README.md, examples/file_and_images/*
New Flutter example demonstrating saving images and JSON files to shared widget storage; includes UI, assets, and example-level docs and configs.
Example App — Android
examples/file_and_images/android/...
Full Android example added: Gradle KTS, manifests, Glance widgets & receivers (ImageWidgetHomeWidget, FileWidgetHomeWidget), resources, widget provider XML, and widget logic that reads saved files/images.
Example App — iOS
examples/file_and_images/ios/...
Full iOS example added: WidgetKit providers reading saved paths from app group, entitlements, Xcode project files, asset catalogs, launch/storyboard resources, and AppDelegate.
Build & Workspace
pubspec.yaml, .github/cspell.yaml, examples/file_and_images/android/*, examples/file_and_images/ios/*
Added example to workspace, Gradle wrapper/settings, CocoaPods Podfile, Xcode project files; updated cspell ignore patterns and custom words.
Example Ignores & Misc
examples/file_and_images/.gitignore, examples/file_and_images/android/.gitignore, examples/file_and_images/ios/.gitignore, examples/file_and_images/.metadata, examples/file_and_images/analysis_options.yaml
Added platform-specific .gitignore files, Flutter metadata, and example analysis options.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant App as Flutter App
participant API as home_widget API
participant Platform as Platform (iOS/Android)
participant FS as File System
participant Widget as Native Widget Extension

App->>API: call saveFile(key, bytes, extension)
API->>API: validate key & extension
API->>Platform: request container/app-support path
Platform-->>API: returns container path
API->>FS: write bytes to {container}/home_widget/{key}.{ext}
API->>API: call saveWidgetData(key, absolutePath)
API->>Platform: method channel persist key→path
Platform-->>API: response OK
Note over Widget,Platform: Native widget reads stored path via shared storage
Widget->>FS: open file at returned absolute path
Widget-->>Widget: render content (image or JSON)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary feature addition: support for HomeWidget.saveFile and HomeWidget.saveImage APIs.
Description check ✅ Passed The description covers main objectives (two new APIs and file-deletion behavior), and all required checklist items are marked as completed. Tests, documentation, and examples are confirmed as updated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/save-files

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (fa7453a) to head (269b635).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #409   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines          115       163   +48     
=========================================
+ Hits           115       163   +48     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This was referenced Apr 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
packages/home_widget/test/utils/test_png.dart (1)

7-7: Optional: add explicit type for the decoded fixture bytes.

Inference works, but a concrete type (Uint8List or List<int>) makes test intent clearer.

Suggested tweak
+import 'dart:typed_data';
 import 'dart:convert';
@@
-final kTestPngBytes = base64Decode(kTestPngBase64);
+final Uint8List kTestPngBytes = base64Decode(kTestPngBase64);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/test/utils/test_png.dart` at line 7, The test fixture
variable kTestPngBytes is currently declared via inference; make its type
explicit (e.g., Uint8List or List<int>) to clarify intent—update the declaration
of kTestPngBytes (the value produced by base64Decode(kTestPngBase64)) to have
the chosen concrete type so readers and tooling see it as a byte buffer for PNG
test data.
examples/file_and_images/README.md (1)

1-3: Expand this README with purpose and quick-start steps.

Line 3 is too generic for an example meant to showcase saveFile/saveImage; a short “what this demonstrates” and “how to run” section would make it much more usable.

📝 Suggested README update
 # file_and_images
 
-A new Flutter project.
+Example app demonstrating `HomeWidget.saveFile` and `HomeWidget.saveImage`.
+
+## What it shows
+- Saving a local file for widget consumption
+- Saving an image for widget rendering
+- Clearing widget data with file cleanup behavior
+
+## Run
+```bash
+flutter pub get
+flutter run
+```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/file_and_images/README.md` around lines 1 - 3, Update the README.md
for the file_and_images example to explain what the example demonstrates (that
it showcases the saveFile and saveImage APIs), list quick-start steps to run it
(e.g., fetch dependencies with "flutter pub get" and run with "flutter run"),
and include a short usage note describing where saved files/images are placed
and how to trigger the demo actions in the app; reference the example name
"file_and_images" and the functions "saveFile" and "saveImage" so readers
immediately know the intent and how to run it.
examples/file_and_images/ios/RunnerTests/RunnerTests.swift (1)

7-10: Replace the placeholder test with an assertion-based test (or remove it).

Lines 7–10 currently provide no verification and can create a false sense of coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/file_and_images/ios/RunnerTests/RunnerTests.swift` around lines 7 -
10, The placeholder test testExample() in RunnerTests currently has no
assertions—either remove it or replace it with a real assertion-based test;
update the testExample() method inside the RunnerTests class to perform at least
one XCTAssert (for example XCTAssertTrue, XCTAssertEqual, or XCTAssertNotNil)
that verifies a deterministic property (app starts, bundle identifier, or a
known value) so the test provides real verification instead of being empty.
docs/usage/sync-data.mdx (1)

20-20: Clarify method-to-payload mapping for readability.

Nice addition. Consider explicitly stating saveFile for arbitrary bytes/files and saveImage for image providers to remove any ambiguity in this sentence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/usage/sync-data.mdx` at line 20, Clarify the mapping between methods and
payload types: explicitly state that for arbitrary binary data or files you
should use saveFile (e.g., JSON, blobs, any bytes) and for image-specific
payloads coming from image providers you should use saveImage, and that both use
the same id which getWidgetData will return as the absolute file path for native
opening; mention saveFile, saveImage, and getWidgetData by name so readers can
locate the methods.
examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidget.kt (1)

43-44: Prefer sampled bitmap decoding to avoid widget memory pressure.

Line 43 and Line 44 decode the original file at full size. Large images can cause avoidable memory spikes in the widget process. Consider decoding with BitmapFactory.Options and an inSampleSize.

♻️ Proposed refactor
-    val bitmap =
-        imagePath?.takeIf { File(it).isFile }?.let { path -> BitmapFactory.decodeFile(path) }
+    val bitmap =
+        imagePath?.takeIf { File(it).isFile }?.let { path ->
+          decodeSampledBitmap(path, reqWidth = 1024, reqHeight = 1024)
+        }
private fun decodeSampledBitmap(path: String, reqWidth: Int, reqHeight: Int) =
    BitmapFactory.Options().run {
      inJustDecodeBounds = true
      BitmapFactory.decodeFile(path, this)

      inSampleSize = 1
      while ((outWidth / inSampleSize) > reqWidth || (outHeight / inSampleSize) > reqHeight) {
        inSampleSize *= 2
      }

      inJustDecodeBounds = false
      BitmapFactory.decodeFile(path, this)
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidget.kt`
around lines 43 - 44, The current direct BitmapFactory.decodeFile call that
assigns bitmap (using imagePath) can OOM on large images; replace it by
implementing a sampled decoder (e.g., decodeSampledBitmap(path: String,
reqWidth: Int, reqHeight: Int): Bitmap?) which uses BitmapFactory.Options with
inJustDecodeBounds to compute an inSampleSize power-of-two and then decodes with
that inSampleSize, and call that helper instead of BitmapFactory.decodeFile when
creating the bitmap in ImageWidgetHomeWidget (replace the bitmap assignment that
uses imagePath). Use the widget's target width/height (or a sensible max
dimension) as reqWidth/reqHeight so the decoded bitmap is appropriately
downsampled.
examples/file_and_images/ios/Runner.xcodeproj/project.pbxproj (1)

762-767: Minor: Duplicate entry in LD_RUNPATH_SEARCH_PATHS.

The @executable_path/../../Frameworks path appears twice in the Profile configuration for ImageWidgetHomeWidget. This is harmless but creates unnecessary noise.

The same duplication exists for FileWidgetHomeWidget's Profile configuration (lines 1023-1028).

🔧 Suggested fix for ImageWidgetHomeWidget Profile config
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
-					"@executable_path/../../Frameworks",
 				);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/file_and_images/ios/Runner.xcodeproj/project.pbxproj` around lines
762 - 767, Remove the duplicate "@executable_path/../../Frameworks" entry from
the LD_RUNPATH_SEARCH_PATHS arrays in the Profile configurations for
ImageWidgetHomeWidget and FileWidgetHomeWidget; locate the
LD_RUNPATH_SEARCH_PATHS block (the tuple containing "$(inherited)",
"@executable_path/Frameworks", and two "@executable_path/../../Frameworks"
entries) and delete the redundant "@executable_path/../../Frameworks" so each
path appears only once.
packages/home_widget/lib/src/home_widget.dart (1)

163-185: Good defensive validation for file path components.

The _normalizeExtension and _validateKey helpers prevent path traversal and injection issues by rejecting separators (/, \, ..) and ensuring non-empty values. Note these are debug-only assertions, so in release builds invalid input would still be accepted.

💡 Consider runtime validation for release builds

If invalid keys/extensions in release builds could cause security or data integrity issues, consider throwing instead of asserting:

 static void _validateKey(String key) {
-  assert(key.isNotEmpty, 'key must not be empty');
-  assert(
-    !key.contains('/') &&
-        !key.contains(r'\') &&
-        !key.contains('..') &&
-        !key.contains(' '),
-    'key must not contain /, \\, .., or spaces',
-  );
+  if (key.isEmpty ||
+      key.contains('/') ||
+      key.contains(r'\') ||
+      key.contains('..') ||
+      key.contains(' ')) {
+    throw ArgumentError('key must be non-empty and not contain /, \\, .., or spaces');
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/lib/src/home_widget.dart` around lines 163 - 185, The
current validation in _normalizeExtension and _validateKey uses debug-only
assert checks, so in release invalid inputs slip through; change these
assertions to real runtime checks that throw descriptive exceptions (e.g.,
ArgumentError or FormatException) instead of using assert: in
_normalizeExtension, after trimming and removing a leading '.', throw if
ext.isEmpty or if ext contains '/', '\\' or '..' with a clear message; in
_validateKey, perform the same checks and throw if key.isEmpty or contains '/',
'\\', '..', or spaces, using consistent exception types/messages so callers
receive failures in release builds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@examples/file_and_images/android/app/src/main/res/drawable/launch_background.xml`:
- Line 4: The launch splash currently hardcodes `@android`:color/white in
launch_background.xml which causes a white flash in dark mode; replace that
hardcoded reference with a night-aware resource by either (a) creating
drawable-night/launch_background.xml that sets a dark background for the same
drawable name, or (b) change the item to reference a color resource (e.g.,
`@color/launch_background`) and add a values-night/colors.xml providing a dark
variant; update any references in styles.xml that point to this drawable to
continue using the same resource name so the night qualifier is picked up.

In `@examples/file_and_images/android/build.gradle.kts`:
- Around line 1-17: Add cspell ignore directives for the Gradle DSL identifiers
causing false positives: add a single-line comment at the top of the file like
"// cspell:ignore allprojects subprojects newSubprojectBuildDir newBuildDir" so
the spellchecker skips those tokens; this targets the symbols allprojects,
subprojects, newSubprojectBuildDir and newBuildDir referenced in the file.

In `@examples/file_and_images/lib/main.dart`:
- Around line 92-96: The ImageProvider creation uses _imageUrl! when imageType
== ImageType.network which can throw if _imageUrl is null/empty; update the
switch in the widget that builds ImageProvider (the block creating ImageProvider
and using ImageType.flutter, ImageType.dash, ImageType.network) to guard against
null/empty _imageUrl by either using a fallback AssetImage or excluding the
NetworkImage branch until a valid URL exists, or disable the "Update" action
when imageType == ImageType.network and _imageUrl is null/empty; ensure checks
reference ImageType.network and _imageUrl (not using _imageUrl!) and pick a
clear fallback (e.g., AssetImage('assets/placeholder.png')) or prevent the
update action.

In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 200-218: The code builds a file path from a possibly null
directory returned by PathProviderFoundation.getContainerPath; add a null check
immediately after assigning directory (before computing path =
'$directory/home_widget/$key.$ext') and handle it by throwing a clear exception
or returning an error (include contextual info like HomeWidget.groupId and
suggest verifying app group configuration) so the app never constructs
"null/..." paths; update the block around Platform.isIOS and the later path
construction (the local variable directory and the final path computation) to
perform this validation and fail fast with a helpful message.

---

Nitpick comments:
In `@docs/usage/sync-data.mdx`:
- Line 20: Clarify the mapping between methods and payload types: explicitly
state that for arbitrary binary data or files you should use saveFile (e.g.,
JSON, blobs, any bytes) and for image-specific payloads coming from image
providers you should use saveImage, and that both use the same id which
getWidgetData will return as the absolute file path for native opening; mention
saveFile, saveImage, and getWidgetData by name so readers can locate the
methods.

In
`@examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidget.kt`:
- Around line 43-44: The current direct BitmapFactory.decodeFile call that
assigns bitmap (using imagePath) can OOM on large images; replace it by
implementing a sampled decoder (e.g., decodeSampledBitmap(path: String,
reqWidth: Int, reqHeight: Int): Bitmap?) which uses BitmapFactory.Options with
inJustDecodeBounds to compute an inSampleSize power-of-two and then decodes with
that inSampleSize, and call that helper instead of BitmapFactory.decodeFile when
creating the bitmap in ImageWidgetHomeWidget (replace the bitmap assignment that
uses imagePath). Use the widget's target width/height (or a sensible max
dimension) as reqWidth/reqHeight so the decoded bitmap is appropriately
downsampled.

In `@examples/file_and_images/ios/Runner.xcodeproj/project.pbxproj`:
- Around line 762-767: Remove the duplicate "@executable_path/../../Frameworks"
entry from the LD_RUNPATH_SEARCH_PATHS arrays in the Profile configurations for
ImageWidgetHomeWidget and FileWidgetHomeWidget; locate the
LD_RUNPATH_SEARCH_PATHS block (the tuple containing "$(inherited)",
"@executable_path/Frameworks", and two "@executable_path/../../Frameworks"
entries) and delete the redundant "@executable_path/../../Frameworks" so each
path appears only once.

In `@examples/file_and_images/ios/RunnerTests/RunnerTests.swift`:
- Around line 7-10: The placeholder test testExample() in RunnerTests currently
has no assertions—either remove it or replace it with a real assertion-based
test; update the testExample() method inside the RunnerTests class to perform at
least one XCTAssert (for example XCTAssertTrue, XCTAssertEqual, or
XCTAssertNotNil) that verifies a deterministic property (app starts, bundle
identifier, or a known value) so the test provides real verification instead of
being empty.

In `@examples/file_and_images/README.md`:
- Around line 1-3: Update the README.md for the file_and_images example to
explain what the example demonstrates (that it showcases the saveFile and
saveImage APIs), list quick-start steps to run it (e.g., fetch dependencies with
"flutter pub get" and run with "flutter run"), and include a short usage note
describing where saved files/images are placed and how to trigger the demo
actions in the app; reference the example name "file_and_images" and the
functions "saveFile" and "saveImage" so readers immediately know the intent and
how to run it.

In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 163-185: The current validation in _normalizeExtension and
_validateKey uses debug-only assert checks, so in release invalid inputs slip
through; change these assertions to real runtime checks that throw descriptive
exceptions (e.g., ArgumentError or FormatException) instead of using assert: in
_normalizeExtension, after trimming and removing a leading '.', throw if
ext.isEmpty or if ext contains '/', '\\' or '..' with a clear message; in
_validateKey, perform the same checks and throw if key.isEmpty or contains '/',
'\\', '..', or spaces, using consistent exception types/messages so callers
receive failures in release builds.

In `@packages/home_widget/test/utils/test_png.dart`:
- Line 7: The test fixture variable kTestPngBytes is currently declared via
inference; make its type explicit (e.g., Uint8List or List<int>) to clarify
intent—update the declaration of kTestPngBytes (the value produced by
base64Decode(kTestPngBase64)) to have the chosen concrete type so readers and
tooling see it as a byte buffer for PNG test data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 278d839e-1290-466b-ae8f-84dcc20474f6

📥 Commits

Reviewing files that changed from the base of the PR and between fa7453a and 826bf56.

⛔ Files ignored due to path filters (25)
  • examples/file_and_images/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • examples/file_and_images/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • examples/file_and_images/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • examples/file_and_images/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • examples/file_and_images/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • examples/file_and_images/assets/dash.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png is excluded by !**/*.png
  • examples/file_and_images/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png is excluded by !**/*.png
  • packages/home_widget/example/assets/integration_test.png is excluded by !**/*.png
📒 Files selected for processing (68)
  • docs.json
  • docs/features/render-flutter-widgets.mdx
  • docs/features/save-files.mdx
  • docs/usage/sync-data.mdx
  • examples/file_and_images/.gitignore
  • examples/file_and_images/.metadata
  • examples/file_and_images/README.md
  • examples/file_and_images/analysis_options.yaml
  • examples/file_and_images/android/.gitignore
  • examples/file_and_images/android/app/build.gradle.kts
  • examples/file_and_images/android/app/src/debug/AndroidManifest.xml
  • examples/file_and_images/android/app/src/main/AndroidManifest.xml
  • examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/FileWidgetHomeWidget.kt
  • examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/FileWidgetHomeWidgetReceiver.kt
  • examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidget.kt
  • examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidgetReceiver.kt
  • examples/file_and_images/android/app/src/main/kotlin/es/antonborri/file_and_images/MainActivity.kt
  • examples/file_and_images/android/app/src/main/res/drawable-v21/launch_background.xml
  • examples/file_and_images/android/app/src/main/res/drawable/launch_background.xml
  • examples/file_and_images/android/app/src/main/res/values-night/styles.xml
  • examples/file_and_images/android/app/src/main/res/values/styles.xml
  • examples/file_and_images/android/app/src/main/res/xml/file_widget_home_widget.xml
  • examples/file_and_images/android/app/src/main/res/xml/image_widget_home_widget.xml
  • examples/file_and_images/android/app/src/profile/AndroidManifest.xml
  • examples/file_and_images/android/build.gradle.kts
  • examples/file_and_images/android/gradle.properties
  • examples/file_and_images/android/gradle/wrapper/gradle-wrapper.properties
  • examples/file_and_images/android/settings.gradle.kts
  • examples/file_and_images/ios/.gitignore
  • examples/file_and_images/ios/FileWidgetHomeWidget.entitlements
  • examples/file_and_images/ios/FileWidgetHomeWidget/Info.plist
  • examples/file_and_images/ios/FileWidgetHomeWidget/Widget.swift
  • examples/file_and_images/ios/FileWidgetHomeWidget/WidgetBundle.swift
  • examples/file_and_images/ios/Flutter/AppFrameworkInfo.plist
  • examples/file_and_images/ios/Flutter/Debug.xcconfig
  • examples/file_and_images/ios/Flutter/Release.xcconfig
  • examples/file_and_images/ios/ImageWidgetHomeWidget.entitlements
  • examples/file_and_images/ios/ImageWidgetHomeWidget/Info.plist
  • examples/file_and_images/ios/ImageWidgetHomeWidget/Widget.swift
  • examples/file_and_images/ios/ImageWidgetHomeWidget/WidgetBundle.swift
  • examples/file_and_images/ios/Podfile
  • examples/file_and_images/ios/Runner.xcodeproj/project.pbxproj
  • examples/file_and_images/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • examples/file_and_images/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • examples/file_and_images/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • examples/file_and_images/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  • examples/file_and_images/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  • examples/file_and_images/ios/Runner/AppDelegate.swift
  • examples/file_and_images/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  • examples/file_and_images/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  • examples/file_and_images/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  • examples/file_and_images/ios/Runner/Base.lproj/LaunchScreen.storyboard
  • examples/file_and_images/ios/Runner/Base.lproj/Main.storyboard
  • examples/file_and_images/ios/Runner/Info.plist
  • examples/file_and_images/ios/Runner/Runner-Bridging-Header.h
  • examples/file_and_images/ios/Runner/Runner.entitlements
  • examples/file_and_images/ios/RunnerTests/RunnerTests.swift
  • examples/file_and_images/lib/main.dart
  • examples/file_and_images/pubspec.yaml
  • packages/home_widget/example/integration_test/android_test.dart
  • packages/home_widget/example/integration_test/ios_test.dart
  • packages/home_widget/example/ios/HomeWidgetExampleExtension.entitlements
  • packages/home_widget/example/ios/Runner/Runner.entitlements
  • packages/home_widget/example/pubspec.yaml
  • packages/home_widget/lib/src/home_widget.dart
  • packages/home_widget/test/home_widget_test.dart
  • packages/home_widget/test/utils/test_png.dart
  • pubspec.yaml

Comment thread examples/file_and_images/android/build.gradle.kts
Comment thread examples/file_and_images/lib/main.dart
Comment thread packages/home_widget/lib/src/home_widget.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
examples/file_and_images/README.md (1)

3-3: Polish wording in the intro sentence (Line 3).

The sentence is understandable but reads a bit rough; a quick grammar/casing cleanup improves clarity.

Suggested wording
-A simple Demo app showing how to save Images and Files from Flutter and accessing the Data from native Widgets
+A simple demo app showing how to save images and files from Flutter and access the data from native widgets.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/file_and_images/README.md` at line 3, The intro sentence in the
README is awkwardly worded; replace it with a polished version to fix grammar
and casing: change "A simple Demo app showing how to save Images and Files from
Flutter and accessing the Data from native Widgets" to "A simple demo app
showing how to save images and files from Flutter and access the data from
native widgets." Update the README intro sentence accordingly (the first
descriptive sentence in the file) so casing is consistent and verbs match.
packages/home_widget/example/integration_test/ios_test.dart (1)

38-45: Expand setup cleanup to include new file/image test keys.

Line 42 currently iterates only testData.keys. The added file tests use separate keys, and the deleteFile: false path can leave artifacts between reruns.

♻️ Suggested cleanup adjustment
     setUp(() async {
       // Add Group Id
       await HomeWidget.setAppGroupId('group.es.antonborri.integrationTest');
       // Clear all Data
-      for (final key in testData.keys) {
+      final cleanupKeys = <String>{
+        ...testData.keys,
+        'integration_json_file_key',
+        'integration_png_file_key',
+        'integration_save_image_key',
+        'integration_savefile_clear_key',
+        'integration_savefile_clear_no_delete_key',
+      };
+      for (final key in cleanupKeys) {
         await HomeWidget.saveWidgetData(key, null);
       }
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/example/integration_test/ios_test.dart` around lines 38
- 45, The setup cleanup only clears keys in testData.keys but misses the new
file/image test keys and the case where deleteFile: false can leave artifacts;
update the setUp block (referencing setUp and HomeWidget.saveWidgetData) to also
iterate and clear the file/image test keys (e.g., the constants or list you
added for file tests) and ensure you call HomeWidget.saveWidgetData for those
keys with null and deleteFile: true (or explicitly remove the files via the
appropriate HomeWidget file-delete API) so no artifacts remain between reruns.
packages/home_widget/example/integration_test/android_test.dart (1)

26-30: Include file-API test keys in per-test cleanup.

Line 27 only clears testData keys, but the new file tests use different keys and one path is intentionally left on disk (deleteFile: false at Line 193). This can leak state across reruns.

♻️ Suggested cleanup adjustment
   setUp(() async {
-    for (final key in testData.keys) {
+    final cleanupKeys = <String>{
+      ...testData.keys,
+      'integration_json_file_key',
+      'integration_png_file_key',
+      'integration_save_image_key',
+      'integration_savefile_clear_key',
+      'integration_savefile_clear_no_delete_key',
+    };
+    for (final key in cleanupKeys) {
       await HomeWidget.saveWidgetData(key, null);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/example/integration_test/android_test.dart` around lines
26 - 30, The setup cleanup only clears keys from testData but misses the
file-API test keys (and one file left on disk via deleteFile: false), which can
leak state; update the setUp to also iterate over the file-API test keys and
call HomeWidget.saveWidgetData(key, null) for each of them (or use the file-API
teardown to remove the file path), and for the specific test that used
deleteFile: false ensure the leftover path is explicitly removed during
setup/teardown (or flip deleteFile to true) so no file remains between runs;
reference testData, setUp, HomeWidget.saveWidgetData and the deleteFile flag
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/file_and_images/README.md`:
- Line 29: The docs snippet uses the token Charsets
(File(jsonPath).readText(Charsets.UTF_8)) which cspell flags; either add
"Charsets" to the project's cspell allowlist (cspell.json / words list) or
change the example to avoid the Charsets token (e.g., use a standard alternative
such as java.nio.charset.StandardCharsets.UTF_8 or pass "UTF-8" as the charset)
so the documentation no longer contains the flagged token; update the README.md
example accordingly and keep the call site File(jsonPath).readText(...)
semantics unchanged.

In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 24-37: The auto-delete logic in saveWidgetData is unsafe: when
data==null it fetches getWidgetData(id) and may synchronously delete any string
that happens to be a valid path; change saveWidgetData to (1) treat the returned
value strictly as a stored file path only after verifying it is a String and
that it resides under your app/widget data directory or matches the expected
storage filename pattern (use a canonical/normalized path check), (2) perform
file operations asynchronously (await file.exists() and await file.delete()) to
avoid sync I/O blocking, and (3) only delete when deleteFile==true and the path
validation passes; update references to getWidgetData and the File unlink logic
inside saveWidgetData accordingly.
- Around line 163-185: The current _normalizeExtension and _validateKey use
assert() so checks are stripped in release builds; replace those asserts with
runtime exceptions (e.g., throw ArgumentError or FormatException) so invalid
inputs cannot reach path construction from public APIs (saveFile, saveImage,
renderFlutterWidget). In _normalizeExtension(String extension) keep the trim and
leading-dot removal, then if ext.isEmpty throw a descriptive exception and if
ext contains '/' or '\' or '..' throw an exception stating invalid/forbidden
characters. In _validateKey(String key) check key.isNotEmpty and throw if empty,
and if key contains '/' or '\' or '..' or ' ' throw a descriptive exception;
ensure messages reference the offending parameter for easier debugging.

---

Nitpick comments:
In `@examples/file_and_images/README.md`:
- Line 3: The intro sentence in the README is awkwardly worded; replace it with
a polished version to fix grammar and casing: change "A simple Demo app showing
how to save Images and Files from Flutter and accessing the Data from native
Widgets" to "A simple demo app showing how to save images and files from Flutter
and access the data from native widgets." Update the README intro sentence
accordingly (the first descriptive sentence in the file) so casing is consistent
and verbs match.

In `@packages/home_widget/example/integration_test/android_test.dart`:
- Around line 26-30: The setup cleanup only clears keys from testData but misses
the file-API test keys (and one file left on disk via deleteFile: false), which
can leak state; update the setUp to also iterate over the file-API test keys and
call HomeWidget.saveWidgetData(key, null) for each of them (or use the file-API
teardown to remove the file path), and for the specific test that used
deleteFile: false ensure the leftover path is explicitly removed during
setup/teardown (or flip deleteFile to true) so no file remains between runs;
reference testData, setUp, HomeWidget.saveWidgetData and the deleteFile flag
when making the change.

In `@packages/home_widget/example/integration_test/ios_test.dart`:
- Around line 38-45: The setup cleanup only clears keys in testData.keys but
misses the new file/image test keys and the case where deleteFile: false can
leave artifacts; update the setUp block (referencing setUp and
HomeWidget.saveWidgetData) to also iterate and clear the file/image test keys
(e.g., the constants or list you added for file tests) and ensure you call
HomeWidget.saveWidgetData for those keys with null and deleteFile: true (or
explicitly remove the files via the appropriate HomeWidget file-delete API) so
no artifacts remain between reruns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8a1c27c6-b3a6-4ffa-b37d-bf8e0316dadc

📥 Commits

Reviewing files that changed from the base of the PR and between 21efec8 and a5e6fe0.

📒 Files selected for processing (4)
  • examples/file_and_images/README.md
  • packages/home_widget/example/integration_test/android_test.dart
  • packages/home_widget/example/integration_test/ios_test.dart
  • packages/home_widget/lib/src/home_widget.dart

Comment thread examples/file_and_images/README.md
Comment thread packages/home_widget/lib/src/home_widget.dart
Comment thread packages/home_widget/lib/src/home_widget.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/home_widget/lib/src/home_widget.dart (1)

170-173: ⚠️ Potential issue | 🟠 Major

Harden managed-path detection before delete.

Line 172 uses substring matching only. A path like /.../home_widget/../... still passes and can point outside the managed directory when Line 31 decides deletion eligibility.

🛡️ Suggested hardening
-static bool _isHomeWidgetManagedFilePath(String path) {
-  final normalized = path.replaceAll(r'\', '/');
-  return normalized.contains('/home_widget/');
-}
+static bool _isHomeWidgetManagedFilePath(String path) {
+  final normalized = File(path).absolute.path.replaceAll(r'\', '/');
+  return normalized.contains('/home_widget/') &&
+      !normalized.contains('/../') &&
+      !normalized.endsWith('/..');
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/lib/src/home_widget.dart` around lines 170 - 173, The
_isHomeWidgetManagedFilePath function currently checks for '/home_widget/' via
simple substring matching which can be bypassed by path segments like '../';
change it to normalize and resolve the path components (replace backslashes, use
path normalization/canonicalization to collapse '..' and '.') and then check
that one of the path segments equals 'home_widget' (e.g., split the normalized
path and verify a segment matches exactly) or verify the resolved absolute path
is contained within the intended managed directory root; update
_isHomeWidgetManagedFilePath to perform this canonical check so deletion
eligibility (used elsewhere) cannot be tricked by crafted paths.
🧹 Nitpick comments (1)
packages/home_widget/lib/src/home_widget.dart (1)

257-259: Preserve stack trace when rethrowing save failures.

Line 258 wraps errors but discards the original stack trace, which makes failures harder to debug.

🔧 Suggested refactor
-    } catch (e) {
-      throw Exception('Failed to save file to widget container: $e');
+    } catch (e, st) {
+      Error.throwWithStackTrace(
+        Exception('Failed to save file to widget container: $e'),
+        st,
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/home_widget/lib/src/home_widget.dart` around lines 257 - 259, The
catch block that currently does "catch (e) { throw Exception('Failed to save
file to widget container: $e'); }" discards the original stack trace — change it
to capture the stack trace (catch (e, st)) and rethrow the new Exception while
preserving the original stack trace using
Error.throwWithStackTrace(Exception('Failed to save file to widget container:
$e'), st); this keeps the descriptive message and the original stack trace for
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 226-234: The assert on HomeWidget.groupId is inactive in release
builds and the subsequent force-unwrap (HomeWidget.groupId!) can crash; replace
the assert with an explicit runtime guard that checks HomeWidget.groupId and, if
null, throws a StateError with a clear message (e.g., "No groupId defined. Did
you forget to call `HomeWidget.setAppGroupId`?") before calling
PathProviderFoundation().getContainerPath so getContainerPath is only invoked
with a non-null appGroupIdentifier.

---

Duplicate comments:
In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 170-173: The _isHomeWidgetManagedFilePath function currently
checks for '/home_widget/' via simple substring matching which can be bypassed
by path segments like '../'; change it to normalize and resolve the path
components (replace backslashes, use path normalization/canonicalization to
collapse '..' and '.') and then check that one of the path segments equals
'home_widget' (e.g., split the normalized path and verify a segment matches
exactly) or verify the resolved absolute path is contained within the intended
managed directory root; update _isHomeWidgetManagedFilePath to perform this
canonical check so deletion eligibility (used elsewhere) cannot be tricked by
crafted paths.

---

Nitpick comments:
In `@packages/home_widget/lib/src/home_widget.dart`:
- Around line 257-259: The catch block that currently does "catch (e) { throw
Exception('Failed to save file to widget container: $e'); }" discards the
original stack trace — change it to capture the stack trace (catch (e, st)) and
rethrow the new Exception while preserving the original stack trace using
Error.throwWithStackTrace(Exception('Failed to save file to widget container:
$e'), st); this keeps the descriptive message and the original stack trace for
debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 31daf0b2-b3d2-4ba5-9fbe-3e418b8aa405

📥 Commits

Reviewing files that changed from the base of the PR and between 517d384 and 269b635.

📒 Files selected for processing (5)
  • examples/file_and_images/README.md
  • packages/home_widget/example/integration_test/android_test.dart
  • packages/home_widget/example/integration_test/ios_test.dart
  • packages/home_widget/lib/src/home_widget.dart
  • packages/home_widget/test/home_widget_test.dart
✅ Files skipped from review due to trivial changes (1)
  • examples/file_and_images/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/home_widget/test/home_widget_test.dart

Comment thread packages/home_widget/lib/src/home_widget.dart
@ABausG ABausG merged commit bf965fb into main Apr 11, 2026
17 checks passed
@ABausG ABausG deleted the feat/save-files branch April 11, 2026 14:27
@coderabbitai coderabbitai Bot mentioned this pull request Jun 7, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clear data Using Image.asset with renderFlutterWidget doesn't render Image Show Image in the widget

1 participant