Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ ignorePaths:
"**/gradle.properties",
"cspell.yaml",
"**/build.gradle",
"**/*.kts",
"**/proguard-rules.pro",
"**/.github/**",
"**/.gitignore",
"**/*.intentdefinition"
"**/*.intentdefinition",
"**/ios/Flutter/**",
"**/*.iml"
]
dictionaries:
- names
Expand Down Expand Up @@ -80,3 +83,5 @@ words:
- Codemagic
- lockscreen
- codesign
- Charsets
- savefile
4 changes: 4 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"Analytics",
"/features/analytics"
],
[
"Save files and images",
"/features/save-files"
],
[
"Configurable Widgets",
"/features/configurable-widgets"
Expand Down
2 changes: 2 additions & 0 deletions docs/features/render-flutter-widgets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ var path = await HomeWidget.renderFlutterWidget(
- `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

Under the hood this uses the same file storage and key→path behavior as [`saveFile`](/features/save-files) (PNG extension). For other file types, arbitrary bytes, or saving an [`ImageProvider`](https://api.flutter.dev/flutter/painting/ImageProvider-class.html) without building a widget tree, see [Save files and images](/features/save-files).

## iOS

To retrieve the image and display it in a widget, you can use the following SwiftUI code:
Expand Down
57 changes: 57 additions & 0 deletions docs/features/save-files.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Save files and images
description: Use saveFile and saveImage to store files for Home Screen widgets
---

# Save files and images

Besides primitive values via [`saveWidgetData`](/usage/sync-data), you can write **files** into the same shared area your app and widgets use. The absolute path is stored under your chosen key so native widget code can load the file (for example with `UIImage(contentsOfFile:)` or `BitmapFactory.decodeFile`).

## Flutter

### `saveFile`

Write arbitrary bytes and register the path:

```dart
import 'dart:convert';
import 'dart:typed_data';

final json = jsonEncode({'count': 3});
final path = await HomeWidget.saveFile(
'stats',
Uint8List.fromList(utf8.encode(json)),
extension: 'json',
);
```

- The file is written as `{container}/home_widget/{key}.{extension}`.
- On **iOS** the container is the [app group](/setup/ios) (call `setAppGroupId` first).
- On **Android** it is under the application support directory, in a `home_widget` subdirectory.

`key` must be non-empty and must not contain `/`, `\`, `..`, or spaces. `extension` is normalized (e.g. `json` or `.json`); it must not contain path separators. These rules are enforced with **assertions** in debug builds.

### `saveImage`

Decode an [`ImageProvider`](https://api.flutter.dev/flutter/painting/ImageProvider-class.html) and save the **first frame** as PNG via `saveFile`:

```dart
await HomeWidget.saveImage(
'avatar',
NetworkImage('https://example.com/photo.png'),
);
```

Animated images use the first frame only.

### Relationship to `renderFlutterWidget`

[`renderFlutterWidget`](/features/render-flutter-widgets) renders a widget to a PNG and uses the same storage and `saveWidgetData` path behavior as `saveFile` with extension `png`.

## Native widgets

Use the same approach as for rendered Flutter widgets: read the key with your platform’s widget APIs to get the path string, then load that path as a file or image. See the [iOS](/setup/ios) and [Android](/setup/android) setup guides for accessing shared data from the widget extension or `AppWidgetProvider`.

## Example

See the **[file_and_images](https://github.com/ABausG/home_widget/tree/main/examples/file_and_images)** example app on GitHub for how to use `saveFile`, `saveImage`, and load the stored files from Android and iOS home screen widgets.
4 changes: 4 additions & 0 deletions docs/usage/sync-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ In order to save Data call
HomeWidget.saveWidgetData<String>('id', data);
```

### Files and images

Primitive values (strings, numbers, booleans, etc.) are stored directly in `UserDefaults` / `SharedPreferences`. For **binary payloads** (JSON files, images, or any bytes), write a file with [`saveFile`](/features/save-files) or [`saveImage`](/features/save-files) and use the same **`id`** with [`getWidgetData`](#read-data): the value will be the **absolute path** to the file on disk, which your widget can open natively.

## Read Data

To retrieve the current Data saved in the Widget call
Expand Down
45 changes: 45 additions & 0 deletions examples/file_and_images/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
33 changes: 33 additions & 0 deletions examples/file_and_images/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: android
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: ios
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
40 changes: 40 additions & 0 deletions examples/file_and_images/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# File and images

A simple demo app showing how to save images and files from Flutter and access the data from native widgets.

**Flutter (save)** — [`lib/main.dart`](lib/main.dart):

```dart
await HomeWidget.saveImage(_imageKey, imageProvider);
await HomeWidget.saveWidgetData(_imageTypeKey, imageType.name);
```

```dart
await HomeWidget.saveFile(
_fileJsonKey,
Uint8List.fromList(utf8.encode(json)),
extension: 'json',
);
```

**Android (path → file)** — [`ImageWidgetHomeWidget.kt`](android/app/src/main/kotlin/es/antonborri/file_and_images/ImageWidgetHomeWidget.kt), [`FileWidgetHomeWidget.kt`](android/app/src/main/kotlin/es/antonborri/file_and_images/FileWidgetHomeWidget.kt):

```kotlin
val imagePath = prefs.getString(IMAGE_KEY, null)
BitmapFactory.decodeFile(imagePath) // after `File(path).isFile` check
```

```kotlin
val jsonPath = prefs.getString(FILE_JSON_KEY, null)
File(jsonPath).readText(Charsets.UTF_8) // when path exists and is a file
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

**iOS (path → file)** — [`ImageWidgetHomeWidget/Widget.swift`](ios/ImageWidgetHomeWidget/Widget.swift), [`FileWidgetHomeWidget/Widget.swift`](ios/FileWidgetHomeWidget/Widget.swift):

```swift
UserDefaults(suiteName: appGroupId)?.string(forKey: imageKey) // then UIImage(contentsOfFile:)
```

```swift
UserDefaults(suiteName: appGroupId)?.string(forKey: fileJsonKey) // then Data(contentsOf: URL(fileURLWithPath:))
```
1 change: 1 addition & 0 deletions examples/file_and_images/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flutter_lints/flutter.yaml
14 changes: 14 additions & 0 deletions examples/file_and_images/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/

# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
45 changes: 45 additions & 0 deletions examples/file_and_images/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("org.jetbrains.kotlin.plugin.compose") version "2.2.20"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}

android {
namespace = "es.antonborri.file_and_images"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() }

defaultConfig {
// TODO: Specify your own unique Application ID
// (https://developer.android.com/studio/build/application-id.html).
applicationId = "es.antonborri.file_and_images"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
buildFeatures { compose = true }
}

flutter { source = "../.." }

dependencies { implementation("androidx.glance:glance-appwidget:1.1.1") }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
66 changes: 66 additions & 0 deletions examples/file_and_images/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="file_and_images"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<receiver
android:name="es.antonborri.file_and_images.ImageWidgetHomeWidgetReceiver"
android:label="ImageWidgetHomeWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/image_widget_home_widget" />
</receiver>
<receiver
android:name="es.antonborri.file_and_images.FileWidgetHomeWidgetReceiver"
android:label="FileWidgetHomeWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/file_widget_home_widget" />
</receiver>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
Loading
Loading