Skip to content

Commit

Permalink
v0.3.0 - RenderFlutterWidget (ABausG#140)
Browse files Browse the repository at this point in the history
* Adding method to render flutter widget to image (ABausG#126)

---------

Co-authored-by: Leigha Jarett <[email protected]>
  • Loading branch information
ABausG and leighajarett authored Jul 2, 2023
1 parent f964af9 commit b7663c1
Show file tree
Hide file tree
Showing 16 changed files with 489 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- dev
pull_request:

concurrency:
Expand Down Expand Up @@ -31,11 +32,11 @@ jobs:
run: flutter pub publish --dry-run
- name: Test
run: flutter test --coverage
- uses: VeryGoodOpenSource/[email protected]
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
- uses: VeryGoodOpenSource/[email protected]

android:
name: Android Integration Tests
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ app.*.symbols
.gradle
*.xcworkspacedata
*.jar

# don't check in golden failure output
**/failures/*.png
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.3.0
* Add `renderFlutterWidget` method to save a Flutter Widget as an Image [#126](https://github.com/ABausG/home_widget/pull/126) by [leighajarett](https://github.com/leighajarett)

## 0.2.1
* Update Gradle and Kotlin Versions
* Update to support Flutter 3.10
Expand Down
140 changes: 140 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,143 @@ With home_widget you can use this by following these steps:
```dart
HomeWidget.registerBackgroundCallback(backgroundCallback);
```

### Using images of Flutter widgets

In some cases, you may not want to rewrite UI code in the native frameworks for your widgets.
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,
),
);
}
}
```

<img width="300" alt="Screenshot 2023-06-07 at 12 33 44 PM" src="https://github.com/ABausG/home_widget/assets/21065911/55619584-bc85-4e7e-9fad-17afde2f74df">

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: Size(width: 400, height: 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
}
```

<img width="522" alt="Screenshot 2023-06-07 at 12 57 28 PM" src="https://github.com/ABausG/home_widget/assets/21065911/f7dcdea0-605a-4662-a03a-158831a4e946">

#### Android

1. Add an image UI element to your xml file:
```xml
<ImageView
android:id="@+id/widget_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_below="@+id/headline_description"
android:layout_alignBottom="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="-134dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:background="@android:color/white"
android:scaleType="fitCenter"
android:src="@android:drawable/star_big_on"
android:visibility="visible"
tools:visibility="visible" />
```
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)
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package es.antonborri.home_widget_example
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.BitmapFactory
import android.net.Uri
import android.view.View
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
Expand Down Expand Up @@ -32,6 +34,15 @@ class HomeWidgetExampleProvider : HomeWidgetProvider() {
val message = widgetData.getString("message", null)
setTextViewText(R.id.widget_message, message
?: "No Message Set")
// Show Images saved with `renderFlutterWidget`
val image = widgetData.getString("dashIcon", null)
if (image != null) {
setImageViewBitmap(R.id.widget_img, BitmapFactory.decodeFile(image))
setViewVisibility(R.id.widget_img, View.VISIBLE)
} else {
setViewVisibility(R.id.widget_img, View.GONE)
}

// Detect App opened via Click inside Flutter
val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
context,
Expand Down
6 changes: 6 additions & 0 deletions example/android/app/src/main/res/layout/example_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@
android:layout_height="wrap_content"
android:textSize="18sp"
tools:text="Message" />

<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:id="@+id/widget_img"
android:visibility="gone"/>
</LinearLayout>
12 changes: 12 additions & 0 deletions example/ios/HomeWidgetExample/HomeWidgetExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,25 @@ struct ExampleEntry: TimelineEntry {
struct HomeWidgetExampleEntryView : View {
var entry: Provider.Entry
let data = UserDefaults.init(suiteName:widgetGroupId)
let iconPath: String?

init(entry: Provider.Entry) {
self.entry = entry
iconPath = data?.string(forKey: "dashIcon")

}

var body: some View {
VStack.init(alignment: .leading, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
Text(entry.title).bold().font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
Text(entry.message)
.font(.body)
.widgetURL(URL(string: "homeWidgetExample://message?message=\(entry.message)&homeWidget"))
if (iconPath != nil) {
Image(uiImage: UIImage(contentsOfFile: iconPath!)!).resizable()
.scaledToFill()
.frame(width: 64, height: 64)
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
Expand All @@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.de.zweidenker.homeWidgetExample</string>
<string>YOUR_GROUP_ID</string>
</array>
</dict>
</plist>
8 changes: 8 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ class _MyAppState extends State<MyApp> {
return Future.wait([
HomeWidget.saveWidgetData<String>('title', _titleController.text),
HomeWidget.saveWidgetData<String>('message', _messageController.text),
HomeWidget.renderFlutterWidget(
Icon(
Icons.flutter_dash,
size: 200,
),
logicalSize: Size(200, 200),
key: 'dashIcon',
),
]);
} on PlatformException catch (exception) {
debugPrint('Error Sending Data. $exception');
Expand Down
2 changes: 2 additions & 0 deletions lib/dart_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tags:
golden:
Loading

0 comments on commit b7663c1

Please sign in to comment.