1. Introduction
What are Widgets?
For Flutter developers, the common definition of widget refers to UI components created using the Flutter framework. In the context of this codelab, a widget refers to a mini version of an app that provides a view into the information of the app without opening the app. On Android, widgets live on the home screen. On iOS, they can be added to the home screen, lock screen, or the today view.
How complex can a Widget be?
Most Home Screen widgets are simple. They might contain basic text, simple graphics or, on Android, basic controls. Both Android and iOS limit what UI components and features you can use.
Create the UI for Widgets
Because of these UI limitations, you can't directly draw the UI of a Home Screen widget using the Flutter framework. Instead, you can add widgets created with platform frameworks like Jetpack Compose or SwiftUI to your Flutter app. This codelab discusses examples for sharing resources between your app and the widgets to avoid rewriting complex UI.
What you'll build
In this codelab, you'll build Home Screen widgets on both Android and iOS for a simple Flutter app, using the home_widget package, that allows users to read articles. Your widgets will:
- Show data from your Flutter app.
- Display text using font assets shared from the Flutter app.
- Display an image of a rendered Flutter widget.
This Flutter app includes two screens (or routes):
- The first displays a list of news articles with headlines and descriptions.
- The second displays the full article with a chart created using
CustomPaint
.
.
What you will learn
- How to create Home Screen widgets on iOS and Android.
- How to use the home_widget package to share data between your Home Screen widget and your Flutter app.
- How to reduce the amount of code you need to re-write.
- How to update your Home Screen widget from your Flutter app.
2. Set up your development environment
For both platforms, you need the Flutter SDK and an IDE. You can use your preferred IDE for working with Flutter. This could be Visual Studio Code with the Dart Code and Flutter extensions, or Android Studio or IntelliJ with the Flutter and Dart plugins installed.
To create the iOS Home Screen widget:
- You can run this codelab on a physical iOS device or the iOS simulator.
- You must configure a macOS system with the Xcode IDE. This installs the compiler needed to build the iOS version of your app.
To create the Android Home Screen widget:
- You can run this codelab on a physical Android device or the Android emulator.
- You must configure your development system with Android Studio. This installs the compiler needed to build the Android version of your app.
Get the starter code
Download the initial version of your project from GitHub
From the command line, clone the GitHub repository into a flutter-codelabs directory:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
After cloning the repo, you can find the code for this codelab in the flutter-codelabs/homescreen_codelab directory. This directory contains completed project code for each step in the codelab.
Open the starter app
Open the flutter-codelabs/homescreen_codelab/step_03
directory into your preferred IDE.
Install packages
All required packages were added to the project's pubspec.yaml file. To retrieve the project dependencies, run the following command:
$ flutter pub get
3. Add a basic Home Screen widget
First, add the Home Screen widget using the native platform tooling.
Creating a basic iOS Home Screen widget
Adding an app extension to your Flutter iOS app is similar to adding an app extension to a SwiftUI or UIKit app:
- Run
open ios/Runner.xcworkspace
in a terminal window from your Flutter project directory. Alternatively, right click on the ios folder from VSCode and select Open in Xcode. This opens the default Xcode workspace in your Flutter project. - Select File → New → Target from the menu. This adds a new target to the project.
- A list of templates appears. Select Widget Extension.
- Type "NewsWidgets" into the Product Name box for this widget. Clear both the Include Live Activity and Include Configuration Intent check boxes.
Inspect the sample code
When you add a new target, Xcode generates sample code based on the template you selected. For more on the generated code and WidgetKit, see Apple's app extension documentation.
Debug and test your sample widget
- First, update your Flutter app's configuration. You must do this when you add new packages in your Flutter app and plan to run a target in the project from Xcode. To update your app's configuration, run the following command in your Flutter app directory:
$ flutter build ios --config-only
- Click Runner to bring up a list of targets. Select the widget target that you just created, NewsWidgets, and click Run. Run the widget target from Xcode when you change the iOS widget code.
- The simulator or device screen should display a basic Home Screen widget. If you don't see it, you can add it to the screen. Click and hold on the home screen then click the + in the top left corner.
- Search for the name of the app. For this codelab, search for "Homescreen Widgets"
- Once you add the Home Screen widget, it should display simple text giving the time.
Creating a Basic Android Widget
- To add a Home Screen widget in Android, open the project's build file in Android Studio. You can find this file at android/build.gradle. Alternatively, right click on the android folder from VSCode and select Open in Android Studio.
- After the project builds, locate the app directory in the top-left corner. Add your new Home Screen widget to this directory. Right click the directory, select New -> Widget -> App Widget.
- Android Studio displays a new form. Add basic information about your Home Screen widget including its class name, placement, size, and source language
For this codelab, set the following values:
- Class Name box to NewsWidget
- Minimum Width (cells) dropdown to 3
- Minimum Height (cells) dropdown to 3
Inspect the sample code
When you submit the form, Android Studio creates and updates several files. The changes relevant for this codelab are listed in the table below
Action | Target File | Change |
Update |
| Adds a new receiver which registers the NewsWidget. |
Create |
| Defines Home Screen widget UI. |
Create |
| Defines your Home Screen widget configuration. You can adjust the dimensions or name of your widget in this file. |
Create |
| Contains your Kotlin code to add functionality to your Home Screen widget. |
You can find more detail on these files throughout this codelab.
Debug and test your sample widget
Now, run your application and see the Home Screen widget. Once you build the app, navigate to the application selection screen of your Android device, and long-press the icon for this Flutter project. Select Widgets from the popup menu.
The Android device or emulator displays your default Home Screen widget for Android.
4. Send data from your Flutter app to your Home Screen widget
You can customize the basic Home Screen widget that you created. Update the Home Screen widget to display a headline and summary for a news article. The following screenshot displays an example of the Home Screen widget displaying a headline and summary.
To pass data between your app and Home Screen widget, you need to write Dart and native code. This section divides this process into three parts:
- Writing Dart code in your Flutter app that both Android and iOS can use
- Adding native iOS functionality
- Adding native Android functionality
Using iOS App Groups
To share data between an iOS parent app and a widget extension, both targets must belong to the same app group. To learn more about app groups, see Apple's app group documentation.
Update your bundle identifier:
In Xcode, go to your target's settings. In the Signing & Capabilities tab, check that your team and bundle identifier are set.
Add the App Group to both the Runner target and the NewsWidgetExtension target in Xcode:
Select + Capability -> App Groups and add a new App Group. Repeat for both the Runner (parent app) target and the widget target.
Add the Dart code
Both iOS and Android apps can share data with a Flutter app in a few different ways.To communicate with these apps, leverage the device's local key/value
store. iOS calls this store UserDefaults
, and Android calls this store SharedPreferences
. The home_widget package wraps these APIs to simplify saving data to either platform and enables the Home Screen widgets to pull updated data.
The headline and description data come from the news_data.dart
file. This file contains mock data and a NewsArticle
data class.
lib/news_data.dart
class NewsArticle {
final String title;
final String description;
final String? articleText;
NewsArticle({
required this.title,
required this.description,
this.articleText = loremIpsum,
});
}
Update the headline and description values
To add the functionality to update your Home Screen widget from your Flutter app, navigate to the lib/home_screen.dart
file. Replace the contents of the file with the following code. Then, replace <YOUR APP GROUP>
with the identifier for your App Group.
lib/home_screen.dart
import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; // Add this import
import 'article_screen.dart';
import 'news_data.dart';
// TODO: Replace with your App Group ID
const String appGroupId = '<YOUR APP GROUP>'; // Add from here
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget'; // To here.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
void updateHeadline(NewsArticle newHeadline) { // Add from here
// Save the headline data to the widget
HomeWidget.saveWidgetData<String>('headline_title', newHeadline.title);
HomeWidget.saveWidgetData<String>(
'headline_description', newHeadline.description);
HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
} // To here.
class _MyHomePageState extends State<MyHomePage> {
@override // Add from here
void initState() {
super.initState();
HomeWidget.setAppGroupId(appGroupId);
// Mock read in some data and update the headline
final newHeadline = getNewsStories()[0];
updateHeadline(newHeadline);
} // To here.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Top Stories'),
centerTitle: false,
titleTextStyle: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black)),
body: ListView.separated(
separatorBuilder: (context, idx) {
return const Divider();
},
itemCount: getNewsStories().length,
itemBuilder: (context, idx) {
final article = getNewsStories()[idx];
return ListTile(
key: Key('$idx ${article.hashCode}'),
title: Text(article.title!),
subtitle: Text(article.description!),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ArticleScreen(article: article);
},
),
);
},
);
},
));
}
}
The updateHeadline
function saves the key/value pairs to your device's local storage. The headline_title
key holds the value of newHeadline.title
. The headline_description
key holds the value of the newHeadline.description
. The function also notifies the native platform that new data for the Home Screen widgets can be retrieved and rendered.
Modify the floatingActionButton
Call the updateHeadline
function when the floatingActionButton
is pressed as shown:
lib/article_screen.dart
// New: import the updateHeadline function
import 'home_screen.dart';
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Updating home screen widget...'),
));
// New: call updateHeadline
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
...
With this change, when a user presses the Update Headline button from an article page, the Home Screen widget details are updated.
Update the iOS code to display the article data
To update the Home Screen widget for iOS, use Xcode.
Open the NewsWidgets.swift
file in Xcode:
Configure the TimelineEntry
.
Replace the SimpleEntry
struct with the following code:
ios/NewsWidgets/NewsWidgets.swift
// The date and any data you want to pass into your app must conform to TimelineEntry
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description:String
}
This NewsArticleEntry
struct defines the incoming data to pass into the Home Screen widget when updated. The TimelineEntry
type requires a date parameter.To learn more about the TimelineEntry
protocol, check out Apple's TimelineEntry documentation.
Edit the View
that displays the content
Modify your Home Screen widget to display the news article headline and description instead of the date. To display text in SwiftUI, use the Text
view. To stack views on top of each other in SwiftUI, use the VStack
view.
Replace the generated NewsWidgetEntryView
view with the following code:
ios/NewsWidgets/NewsWidgets.swift
//View that holds the contents of the widget
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.title)
Text(entry.description)
}
}
}
Edit the provider to tell the Home Screen widget when and how to update
Replace the existing Provider
with the following code. Then, substitute your app group identifier for <YOUR APP GROUP>:
ios/NewsWidgets/NewsWidgets.swift
struct Provider: TimelineProvider {
// Placeholder is used as a placeholder when the widget is first displayed
func placeholder(in context: Context) -> NewsArticleEntry {
// Add some placeholder title and description, and get the current date
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
}
// Snapshot entry represents the current time and state
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview{
entry = placeholder(in: context)
}
else{
// Get the data from the user defaults to display
let userDefaults = UserDefaults(suiteName: <YOUR APP GROUP>)
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
entry = NewsArticleEntry(date: Date(), title: title, description: description)
}
completion(entry)
}
// getTimeline is called for the current and optionally future times to update the widget
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
// This just uses the snapshot function you defined earlier
getSnapshot(in: context) { (entry) in
// atEnd policy tells widgetkit to request a new entry after the date has passed
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
The Provider
in the previous code conforms to a TimelineProvider
. Provider
has three different methods:
- The
placeholder
method generates a placeholder entry when the user first previews the Home Screen widget.
- The
getSnapshot
method reads the data from user defaults and generates the entry for the current time. - The
getTimeline
method returns timeline entries. This helps when you have predictable points in time to update your content. This codelab uses the getSnapshot function to get the current state. The.atEnd
method tells the Home Screen widget to refresh the data after the current time passes.
Comment out the NewsWidgets_Previews
Using previews is out of scope for this codelab. For more details on previewing SwiftUI Home Screen widgets see Apple's Documentation on Debugging Widgets.
Save all files and re-run the app and widget target.
Run the targets again to validate that the app and Home Screen widget work.
- Select the app schema in Xcode to run the app target.
- Select the extension schema in Xcode to run the extension target.
- Navigate to an article page in the app.
- Click the button to update the headline. The Home Screen widget should also update the headline.
Update the Android code
Add the Home Screen widget XML.
In Android Studio, update the files generated in the previous step.Open the res/layout/news_widget.xml
file. It defines the structure and layout of your home screen widget. Select Code in the top right hand corner and replace the contents of that file with the following code:
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
</RelativeLayout>
This XML defines two text views, one for the article headline and the other for the article description. These text views also define styling. You will return to this file throughout this codelab.
Update NewsWidget functionality
Open the NewsWidget.kt
Kotlin source code file. This file contains a generated class called NewsWidget
that extends the AppWidgetProvider
class.
The NewsWidget
class contains three methods from its superclass. You'll modify the onUpdate
method. Android calls this method for widgets at fixed intervals.
Replace the contents of the NewsWidget.kt
file with the following code:
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
// New import.
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
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 {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
Now, when onUpdate
is called, Android gets the newest values from local storage using the widgetData.getString()
method, and then calls setTextViewText
to change the text displayed on the Home Screen widget.
Test the updates
Test the app to ensure your Home Screen widgets update with new data. To update the data, use the Update Home Screen FloatingActionButton
on the article pages. Your Home Screen widget should update with the article title.
5. Using Flutter app custom fonts in your iOS Home Screen widget
So far, you have configured the Home Screen widget to read data that the Flutter app provides. The Flutter app includes a custom font that you might want to use in the Home Screen widget. You can use the custom font in your iOS Home Screen widget. Using custom fonts in Home Screen widgets is not available on Android.
Update the iOS code
Flutter stores its assets in the mainBundle of iOS applications. You can access assets in this bundle from your Home Screen widget code.
In the NewsWidgetsEntryView struct in your NewsWidgets.swift file, make the following changes
Create a helper function to get the path to the Flutter asset directory:
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Add the helper function.
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
...
}
Register the font using the URL to your custom font file.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Register the font.
init(entry: Provider.Entry){
self.entry = entry
CTFontManagerRegisterFontsForURL(bundle.appending(path: "/fonts/Chewy-Regular.ttf") as CFURL, CTFontManagerScope.process, nil)
}
...
}
Update the headline Text view to use your custom font.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
var body: some View {
VStack {
// Update the following line.
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description)
}
}
...
}
When you run your Home Screen widget, it now uses the custom font for the headline as displayed in the following image:
6. Rendering Flutter widgets as an image
In this section, you'll display a graph from your Flutter app as a Home Screen widget.
This widget provides a greater challenge than the text that you've displayed on the homescreen. It's far easier to display the Flutter chart as an image rather than try to recreate it using native UI components.
Code your Home Screen widget to render your Flutter chart as a PNG file. Your Home Screen widget can display that image.
Write the Dart code
On the Dart side, add the renderFlutterWidget
method from the home_widget package. This method takes a widget, a filename, and a key. It returns an image of the Flutter widget and saves it to a shared container. Provide the image name in your code and ensure the Home Screen widget can access the container. The key
saves the full file path as a string in the device's local storage. This allows the Home Screen widget to find the file if the name changes in the Dart code.
For this codelab, the LineChart
class in the lib/article_screen.dart
file represents the chart. Its build method returns a CustomPainter that paints this chart to the screen.
To implement this feature, open the lib/article_screen.dart
file. Import the home_widget package. Next, replace the code in the _ArticleScreenState
class with the following code:
lib/article_screen.dart
import 'package:flutter/material.dart';
// New: import the home_widget package.
import 'package:home_widget/home_widget.dart';
import 'home_screen.dart';
import 'news_data.dart';
...
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
String? imagePath;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title!),
),
// New: add this FloatingActionButton
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: [
Text(
widget.article.description!,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
const SizedBox(height: 20.0),
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
],
),
);
}
}
This example makes three changes to the _ArticleScreenState
class.
Creates a GlobalKey
The GlobalKey
gets the context of the specific widget, which is needed to get the size of that widget .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
Adds imagePath
The imagePath
property stores the image's location where the Flutter widget is rendered.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
Adds the key to the widget to render
The _globalKey
contains the Flutter widget that is rendered to the image. In this case, the Flutter widget is the Center that contains the LineChart
.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- Saves the widget as an image
The renderFlutterWidget
method is called when the user clicks the floatingActionButton
. The method saves the resulting PNG file as "screenshot" to the shared container directory. The method also saves the full path to the image as the filename key in the device storage.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
...
}
Update the iOS code
For iOS, update the code to get the file path from storage and display the file as an image using SwiftUI.
Open the NewsWidgets.swift
file to make the following changes:
Add filename
and displaySize
to the NewsArticleEntry
struct
The filename
property holds the string that represents the path to the image file. The displaySize
property holds the size of the Home Screen widget on the user's device. The size of the Home Screen widget comes from the context
.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
Update the placeholder
function
Include a placeholder filename
and displaySize
.
ios/NewsWidgets/NewsWidgets.swift
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description", filename: "No screenshot available", displaySize: context.displaySize)
}
Get the filename from userDefaults
in getSnapshot
This sets the filename
variable to the filename
value in the userDefaults
storage when the Home Screen widget updates.
ios/NewsWidgets/NewsWidgets.swift
func getSnapshot(
...
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
// New: get fileName from key/value store
let filename = userDefaults?.string(forKey: "filename") ?? "No screenshot available"
...
)
Create ChartImage that displays the image from a path
The ChartImage
View creates an image from the contents of the file generated on the Dart side. Here, you set the size to 50% of the frame.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: create the ChartImage view
var ChartImage: some View {
if let uiImage = UIImage(contentsOfFile: entry.filename) {
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())
}
...
}
Use the ChartImage in the body of NewsWidgetsEntryView
Add the ChartImage view to the body of the NewsWidgetsEntryView to display the ChartImage in the Home Screen widget.
ios/NewsWidgets/NewsWidgets.swift
VStack {
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description).font(.system(size: 12)).padding(10)
// New: add the ChartImage to the NewsWidgetEntryView
ChartImage
}
Test the changes
To test the changes, re-run both your Flutter app (Runner) target and your extension target from Xcode. To see the image, navigate to one of the article pages in the app and press the button to update the Home Screen widget.
Update the Android code
The Android code functions the same as the iOS code.
- Open the
android/app/res/layout/news_widget.xml
file. It contains the UI elements of your Home Screen widget. Replace its contents with the following code:
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
<!--New: add this image view -->
<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" />
</RelativeLayout>
This new code adds an image to the Home Screen widget, which (for now) displays a generic star icon. Replace this star icon with the image that you saved in the Dart code.
- Open the
NewsWidget.kt
file. Replace its content with the following code:
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import java.io.File
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
// New: Add the section below
// Get chart image and put it in the widget, if it exists
val imageName = widgetData.getString("filename", null)
val imageFile = File(imageName)
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 @: ${imageName}")
}
// End new code
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
This Dart code saves a screenshot to local storage with the filename
key. It also gets the image's full path and creates a File
object from it. If the image exists, the Dart code replaces the image in the Home Screen widget with the new image.
- Reload your app and navigate to an article screen. Press Update Homescreen. The Home Screen widget displays the chart.
7. Next Steps
Congratulations!
Congratulations, you succeeded in creating Home Screen widgets for your Flutter iOS and Android apps!
Linking to content in your Flutter app
You might want to direct the user to a specific page in your app, depending on where the user clicks. For example, in the news app from this codelab, you might want the user to see the news article for the displayed headline.
This feature falls outside the scope of this codelab. You can find examples of using a stream that the home_widget package provides to identify app launches from Home Screen widgets and send messages from the Home Screen widget through the URL. To learn more, see the deep linking documentation on docs.flutter.dev.
Updating your widget in the background
In this codelab, you triggered an update of the Home Screen widget using a button. Though this is reasonable for testing, in production code you might want your app to update the Home Screen widget in the background. You can use the workmanager plugin to create background tasks to update resources the Home Screen widget needs. To learn more, check out the Background update section in the home_widget package.
For iOS, you could also have the Home Screen widget make a network request to update its UI. To control the conditions or frequency of that request, use the Timeline. To learn more about using the Timeline, see Apple's "Keeping a widget up to date" documentation.