1. Introdução
O que são widgets?
Para desenvolvedores do Flutter, a definição comum de widget se refere a componentes de interface criados usando o framework Flutter. No contexto deste codelab, um widget se refere a uma versão mini de um app que fornece uma visão das informações do app sem abrir o app. No Android, os widgets ficam na tela inicial. No iOS, eles podem ser adicionados à tela inicial, à tela de bloqueio ou à visualização "Hoje".

Qual a complexidade máxima de um widget?
A maioria dos widgets da tela inicial é simples. Eles podem conter texto básico, gráficos simples ou, no Android, controles básicos. O Android e o iOS limitam os componentes e recursos da interface que você pode usar.

Criar a interface para widgets
Devido a essas limitações da interface, não é possível desenhar diretamente a interface de um widget da tela inicial usando o framework Flutter. Em vez disso, você pode adicionar widgets criados com frameworks de plataforma, como o Jetpack Compose ou o SwiftUI, ao seu app Flutter. Este codelab discute exemplos de compartilhamento de recursos entre o app e os widgets para evitar a reescrita de interfaces complexas.
O que você vai criar
Neste codelab, você vai criar widgets da tela inicial no Android e no iOS para um app simples do Flutter usando o pacote home_widget, que permite aos usuários ler artigos. Seus widgets vão:
- Mostrar dados do seu app Flutter.
- Mostrar texto usando recursos de fonte compartilhados do app Flutter.
- Mostra uma imagem de um widget do Flutter renderizado.

Esse app Flutter inclui duas telas (ou rotas):
- A primeira mostra uma lista de artigos de notícias com títulos e descrições.
- O segundo mostra o artigo completo com um gráfico criado usando
CustomPaint.
.

O que você aprenderá
- Como criar widgets da tela inicial no iOS e no Android.
- Como usar o pacote home_widget para compartilhar dados entre o widget da tela inicial e o app Flutter.
- Como reduzir a quantidade de código que você precisa reescrever.
- Como atualizar o widget da tela inicial no app Flutter.
2. Configurar o ambiente de desenvolvimento
Para as duas plataformas, você precisa do SDK do Flutter e de uma IDE. Você pode usar o ambiente de desenvolvimento integrado de sua preferência para trabalhar com o Flutter. Pode ser o Visual Studio Code com as extensões do Dart Code e do Flutter ou o Android Studio ou IntelliJ com os plug-ins do Flutter e do Dart instalados.
Para criar o widget da tela de início do iOS:
- É possível executar este codelab em um dispositivo iOS físico ou no simulador iOS.
- É necessário configurar um sistema macOS com o ambiente de desenvolvimento integrado Xcode. Isso instala o compilador necessário para criar a versão iOS do seu app.
Para criar o widget da tela inicial do Android:
- Você pode executar este codelab em um dispositivo Android físico ou no emulador do Android.
- Configure seu sistema de desenvolvimento com o Android Studio. Isso instala o compilador necessário para criar a versão Android do seu app.
Acessar o código inicial
Faça o download da versão inicial do seu projeto no GitHub
Na linha de comando, clone o repositório do GitHub em um diretório flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
Depois de clonar o repositório, você encontra o código deste codelab no diretório flutter-codelabs/homescreen_codelab. Esse diretório contém o código do projeto concluído para cada etapa do codelab.
Abra o app inicial
Abra o diretório flutter-codelabs/homescreen_codelab/step_03 no ambiente de desenvolvimento integrado de sua preferência.
Instalar pacotes
Todos os pacotes necessários foram adicionados ao arquivo pubspec.yaml do projeto. Para recuperar as dependências do projeto, execute o seguinte comando:
$ flutter pub get
3. Adicionar um widget básico à tela inicial
Primeiro, adicione o widget da tela inicial usando as ferramentas nativas da plataforma.
Criar um widget básico da tela inicial do iOS
Adicionar uma extensão de app ao seu app Flutter iOS é semelhante a adicionar uma extensão de app a um app SwiftUI ou UIKit:
- Execute
open ios/Runner.xcworkspaceem uma janela de terminal no diretório do projeto do Flutter. Como alternativa, clique com o botão direito do mouse na pasta ios do VSCode e selecione Open in Xcode. Isso abre o espaço de trabalho padrão do Xcode no seu projeto do Flutter. - Selecione Arquivo → Novo → Destino no menu. Isso adiciona uma nova meta ao projeto.
- Uma lista de modelos vai aparecer. Selecione Extensão de widget.
- Digite "NewsWidgets" na caixa Nome do produto para esse widget. Desmarque as caixas de seleção Incluir atividade em tempo real e Incluir intenção de configuração.
Inspecionar o exemplo de código
Quando você adiciona um novo destino, o Xcode gera um exemplo de código com base no modelo selecionado. Para mais informações sobre o código gerado e o WidgetKit, consulte a documentação de extensão de app da Apple .
Depurar e testar o widget de exemplo
- Primeiro, atualize a configuração do app Flutter. Isso é necessário quando você adiciona novos pacotes ao app Flutter e planeja executar um destino no projeto do Xcode. Para atualizar a configuração do app, execute o seguinte comando no diretório do app Flutter:
$ flutter build ios --config-only
- Clique em Runner para abrir uma lista de destinos. Selecione o destino do widget que você acabou de criar, NewsWidgets, e clique em Executar. Execute o destino do widget no Xcode quando você mudar o código do widget iOS.

- A tela do simulador ou do dispositivo precisa mostrar um widget básico da tela inicial. Se ele não aparecer, adicione-o à tela. Clique e pressione a tela inicial e clique no + no canto superior esquerdo.

- Pesquise o nome do app. Para este codelab, pesquise "Widgets da tela inicial".

- Depois de adicionar o widget à tela inicial, ele vai mostrar um texto simples com a hora.
Como criar um widget básico do Android
- Para adicionar um widget da tela inicial no Android, abra o arquivo de build do projeto no Android Studio. Ele está localizado em android/build.gradle. Se preferir, clique com o botão direito do mouse na pasta android no VSCode e selecione Open in Android Studio.
- Depois que o projeto for criado, localize o diretório do app no canto superior esquerdo. Adicione o novo widget da tela inicial a esse diretório. Clique com o botão direito do mouse no diretório e selecione New -> Widget -> App Widget.

- O Android Studio mostra um novo formulário. Adicione informações básicas sobre o widget da tela inicial, incluindo nome da classe, posicionamento, tamanho e idioma de origem.
Para este codelab, defina os seguintes valores:
- Caixa Nome da classe para NewsWidget
- Largura mínima (células) para 3
- No menu suspenso Altura mínima (células), selecione 3.
Inspecionar o exemplo de código
Quando você envia o formulário, o Android Studio cria e atualiza vários arquivos. As mudanças relevantes para este codelab estão listadas na tabela abaixo.
Ação | Arquivo de destino | Mudar |
Atualizar |
| Adiciona um novo receptor que registra o NewsWidget. |
Criar |
| Define a interface do widget da tela inicial. |
Criar |
| Define a configuração do widget da tela inicial. Você pode ajustar as dimensões ou o nome do widget nesse arquivo. |
Criar |
| Contém o código Kotlin para adicionar funcionalidade ao widget da tela inicial. |
Você pode encontrar mais detalhes sobre esses arquivos ao longo deste codelab.
Depurar e testar o widget de exemplo
Agora, execute o aplicativo e confira o widget da tela inicial. Depois de criar o app, navegue até a tela de seleção de aplicativos do seu dispositivo Android e toque e mantenha pressionado o ícone desse projeto do Flutter. Selecione Widgets no menu pop-up.

O dispositivo Android ou emulador mostra o widget padrão da tela inicial para Android.
4. Enviar dados do app Flutter para o widget da tela inicial
É possível personalizar o widget básico da tela inicial que você criou. Atualize o widget da tela inicial para mostrar uma manchete e um resumo de uma notícia. A captura de tela a seguir mostra um exemplo do widget da tela inicial exibindo um título e um resumo.

Para transmitir dados entre o app e o widget da tela inicial, você precisa escrever código Dart e nativo. Esta seção divide esse processo em três partes:
- Escrever código Dart no seu app Flutter que pode ser usado no Android e no iOS
- Como adicionar funcionalidade nativa do iOS
- Como adicionar funcionalidade nativa do Android
Usar grupos de apps iOS
Para compartilhar dados entre um app iOS principal e uma extensão de widget, ambos os destinos precisam pertencer ao mesmo grupo de apps. Para saber mais sobre grupos de apps, consulte a documentação da Apple sobre grupos de apps.
Atualize o identificador do pacote:
No Xcode, acesse as configurações do destino. Na guia Assinatura e recursos, verifique se a equipe e o identificador do pacote estão definidos.
Adicione o grupo de apps aos destinos Runner e NewsWidgetExtension no Xcode:
Selecione + Capacidade -> Grupos de apps e adicione um novo grupo de apps. Repita para o destino do Runner (app principal) e do widget.

Adicionar o código Dart
Os apps iOS e Android podem compartilhar dados com um app Flutter de algumas maneiras diferentes.Para se comunicar com esses apps, use o armazenamento local key/value do dispositivo. O iOS chama esse armazenamento de UserDefaults, e o Android chama de SharedPreferences. O pacote home_widget envolve essas APIs para simplificar o salvamento de dados em qualquer plataforma e permite que os widgets da tela inicial extraiam dados atualizados.

Os dados de título e descrição vêm do arquivo news_data.dart. Esse arquivo contém dados simulados e uma classe de dados NewsArticle.
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,
});
}
Atualizar os valores de título e descrição
Para adicionar a funcionalidade de atualizar o widget da tela inicial no app Flutter, navegue até o arquivo lib/home_screen.dart. Substitua o conteúdo do arquivo pelo código a seguir. Em seguida, substitua <YOUR APP GROUP> pelo identificador do grupo de apps.
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);
},
),
);
},
);
},
));
}
}
A função updateHeadline salva os pares de chave/valor no armazenamento local do dispositivo. A chave headline_title contém o valor de newHeadline.title. A chave headline_description contém o valor do newHeadline.description. A função também notifica a plataforma nativa de que novos dados para os widgets da tela inicial podem ser recuperados e renderizados.
Modificar o floatingActionButton
Chame a função updateHeadline quando o floatingActionButton for pressionado, conforme mostrado:
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'),
),
...
Com essa mudança, quando um usuário pressiona o botão Atualizar título na página de um artigo, os detalhes do widget da tela inicial são atualizados.
Atualizar o código do iOS para mostrar os dados do artigo
Para atualizar o widget da tela inicial no iOS, use o Xcode.
Abra o arquivo NewsWidgets.swift no Xcode:
Configure o TimelineEntry.
Substitua a struct SimpleEntry pelo seguinte código:
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
}
Essa struct NewsArticleEntry define os dados recebidos para serem transmitidos ao widget da tela inicial quando atualizados. O tipo TimelineEntry exige um parâmetro de data.Para saber mais sobre o protocolo TimelineEntry, consulte a documentação TimelineEntry da Apple.
Edite o View que mostra o conteúdo
Modifique o widget da tela inicial para mostrar o título e a descrição do artigo em vez da data. Para mostrar texto no SwiftUI, use a visualização Text. Para empilhar visualizações umas sobre as outras no SwiftUI, use a visualização VStack.
Substitua a visualização NewsWidgetEntryView gerada pelo seguinte código:
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)
}
}
}
Edite o provedor para informar ao widget da tela inicial quando e como atualizar
Substitua o Provider atual pelo código a seguir. Em seguida, substitua o identificador do grupo de apps por <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)
}
}
}
O Provider no código anterior está em conformidade com um TimelineProvider. O Provider tem três métodos diferentes:
- O método
placeholdergera uma entrada de marcador de posição quando o usuário visualiza o widget da tela inicial pela primeira vez.

- O método
getSnapshotlê os dados dos padrões do usuário e gera a entrada para o horário atual. - O método
getTimelineretorna entradas da linha do tempo. Isso é útil quando você tem pontos previsíveis para atualizar seu conteúdo. Este codelab usa a função getSnapshot para acessar o estado atual. O método.atEndinforma ao widget da tela inicial para atualizar os dados após o horário atual.
Comente o NewsWidgets_Previews
O uso de prévias está fora do escopo deste codelab. Para mais detalhes sobre como visualizar widgets da tela de início do SwiftUI, consulte a documentação da Apple sobre depuração de widgets.
Salve todos os arquivos e execute novamente o destino do app e do widget.
Execute os destinos novamente para validar se o app e o widget da tela inicial funcionam.
- Selecione o esquema do app no Xcode para executar o destino do app.
- Selecione o esquema de extensão no Xcode para executar o destino da extensão.
- Acesse a página de um artigo no app.
- Clique no botão para atualizar o título. O widget da tela inicial também vai atualizar o título.
Atualizar o código do Android
Adicione o XML do widget da tela inicial.
No Android Studio, atualize os arquivos gerados na etapa anterior.Abra o arquivo res/layout/news_widget.xml. Ele define a estrutura e o layout do widget da tela inicial. Selecione Código no canto superior direito e substitua o conteúdo desse arquivo pelo seguinte código:
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>
Esse XML define duas visualizações de texto, uma para o título e outra para a descrição do artigo. Essas visualizações de texto também definem o estilo. Você vai voltar a esse arquivo ao longo deste codelab.
Atualizar a funcionalidade do NewsWidget
Abra o arquivo de código-fonte Kotlin NewsWidget.kt. Esse arquivo contém uma classe gerada chamada NewsWidget que estende a classe AppWidgetProvider.
A classe NewsWidget contém três métodos da superclasse. Você vai modificar o método onUpdate. O Android chama esse método para widgets em intervalos fixos.
Substitua o conteúdo do arquivo NewsWidget.kt pelo seguinte código:
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)
}
}
}
Agora, quando onUpdate é chamado, o Android recebe os valores mais recentes do armazenamento local usando o método the widgetData.getString() e chama setTextViewText para mudar o texto mostrado no widget da tela inicial.
Testar as atualizações
Teste o app para garantir que os widgets da tela inicial sejam atualizados com novos dados. Para atualizar os dados, use a opção Atualizar tela inicial FloatingActionButton nas páginas dos artigos. O widget da tela inicial será atualizado com o título do artigo.

5. Usar fontes personalizadas do app Flutter no widget da tela inicial do iOS
Até agora, você configurou o widget da tela inicial para ler os dados fornecidos pelo app Flutter. O app do Flutter inclui uma fonte personalizada que você pode usar no widget da tela inicial. Você pode usar a fonte personalizada no widget da tela de início do iOS. Não é possível usar fontes personalizadas em widgets da tela inicial no Android.
Atualizar o código do iOS
O Flutter armazena os recursos no mainBundle de aplicativos iOS. Você pode acessar os recursos nesse pacote no código do widget da tela inicial.
Na struct NewsWidgetsEntryView no arquivo NewsWidgets.swift, faça as seguintes mudanças:
Crie uma função auxiliar para receber o caminho do diretório de recursos do Flutter:
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
}
...
}
Registre a fonte usando o URL do arquivo de fonte personalizada.
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)
}
...
}
Atualize a visualização de texto do título para usar sua fonte personalizada.
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)
}
}
...
}
Quando você executa o widget da tela inicial, ele usa a fonte personalizada para o título, conforme mostrado na imagem a seguir:

6. Renderizar widgets do Flutter como uma imagem
Nesta seção, você vai mostrar um gráfico do seu app Flutter como um widget da tela inicial.
Esse widget oferece um desafio maior do que o texto mostrado na tela inicial. É muito mais fácil mostrar o gráfico do Flutter como uma imagem do que tentar recriá-lo usando componentes de UI nativos.
Codifique o widget da tela inicial para renderizar o gráfico do Flutter como um arquivo PNG. O widget da tela inicial pode mostrar essa imagem.
Escrever o código Dart
No lado do Dart, adicione o método renderFlutterWidget do pacote home_widget. Esse método usa um widget, um nome de arquivo e uma chave. Ele retorna uma imagem do widget do Flutter e a salva em um contêiner compartilhado. Forneça o nome da imagem no seu código e verifique se o widget da tela inicial pode acessar o contêiner. O key salva o caminho completo do arquivo como uma string no armazenamento local do dispositivo. Isso permite que o widget da tela inicial encontre o arquivo se o nome mudar no código Dart.
Neste codelab, a classe LineChart no arquivo lib/article_screen.dart representa o gráfico. O método de build dele retorna um CustomPainter que pinta esse gráfico na tela.
Para implementar esse recurso, abra o arquivo lib/article_screen.dart. Importe o pacote home_widget. Em seguida, substitua o código na classe _ArticleScreenState pelo seguinte:
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!),
],
),
);
}
}
Este exemplo faz três mudanças na classe _ArticleScreenState.
Cria uma GlobalKey
O GlobalKey recebe o contexto do widget específico, que é necessário para saber o tamanho dele .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
Adiciona imagePath
A propriedade imagePath armazena o local da imagem em que o widget do Flutter é renderizado.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
Adiciona a chave ao widget para renderizar
O _globalKey contém o widget do Flutter renderizado na imagem. Nesse caso, o widget do Flutter é o Center, que contém o LineChart.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- Salva o widget como uma imagem
O método renderFlutterWidget é chamado quando o usuário clica no floatingActionButton. O método salva o arquivo PNG resultante como "screenshot" no diretório do contêiner compartilhado. O método também salva o caminho completo da imagem como a chave do nome do arquivo no armazenamento do dispositivo.
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);
},
...
}
Atualizar o código do iOS
Para iOS, atualize o código para receber o caminho do arquivo do armazenamento e mostre o arquivo como uma imagem usando o SwiftUI.
Abra o arquivo NewsWidgets.swift para fazer as seguintes mudanças:
Adicione filename e displaySize à struct NewsArticleEntry
A propriedade filename contém a string que representa o caminho para o arquivo de imagem. A propriedade displaySize contém o tamanho do widget da tela inicial no dispositivo do usuário. O tamanho do widget da tela inicial vem do context.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
Atualize a função placeholder
Inclua um marcador de posição filename e 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)
}
Extraia o nome do arquivo de userDefaults em getSnapshot.
Isso define a variável filename como o valor filename no armazenamento userDefaults quando o widget da tela inicial é atualizado.
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"
...
)
Crie um ChartImage que mostre a imagem de um caminho
A visualização ChartImage cria uma imagem com base no conteúdo do arquivo gerado no lado do Dart. Aqui, você define o tamanho como 50% do 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 o ChartImage no corpo de NewsWidgetsEntryView
Adicione a visualização ChartImage ao corpo da NewsWidgetsEntryView para mostrar o ChartImage no widget da tela inicial.
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
}
Testar as mudanças
Para testar as mudanças, execute novamente o destino do app Flutter (Runner) e o destino da extensão no Xcode. Para ver a imagem, navegue até uma das páginas de artigos no app e pressione o botão para atualizar o widget da tela inicial.

Atualizar o código do Android
O código Android funciona da mesma forma que o código iOS.
- Abra o arquivo
android/app/res/layout/news_widget.xml. Ele contém os elementos da interface do widget da tela inicial. Substitua o conteúdo dele pelo seguinte código:
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>
Esse novo código adiciona uma imagem ao widget da tela inicial, que (por enquanto) mostra um ícone de estrela genérico. Substitua esse ícone de estrela pela imagem que você salvou no código Dart.
- Abra o arquivo
NewsWidget.kt. Substitua o conteúdo dele pelo seguinte código:
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)
}
}
}
Esse código Dart salva uma captura de tela no armazenamento local com a chave filename. Ele também recebe o caminho completo da imagem e cria um objeto File com base nele. Se a imagem existir, o código Dart vai substituir a imagem no widget da tela inicial pela nova.
- Recarregue o app e navegue até a tela de um artigo. Pressione Atualizar tela inicial. O widget da tela inicial mostra o gráfico.
7. Próximas etapas
Parabéns!
Parabéns, você criou widgets da tela inicial para seus apps do Flutter em iOS e Android.
Como vincular conteúdo no seu app Flutter
Dependendo de onde o usuário clicar, talvez você queira direcioná-lo a uma página específica do app. Por exemplo, no app de notícias deste codelab, talvez você queira que o usuário veja o artigo da manchete exibida.
Esse recurso está fora do escopo deste codelab. Você pode encontrar exemplos de como usar um fluxo fornecido pelo pacote home_widget para identificar inicializações de apps em widgets da tela inicial e enviar mensagens do widget da tela inicial pelo URL. Para saber mais, consulte a documentação sobre links diretos em docs.flutter.dev.
Atualizar o widget em segundo plano
Neste codelab, você acionou uma atualização do widget da tela inicial usando um botão. Embora isso seja razoável para testes, no código de produção, talvez você queira que o app atualize o widget da tela inicial em segundo plano. Você pode usar o plug-in workmanager para criar tarefas em segundo plano e atualizar os recursos necessários para o widget da tela inicial. Para saber mais, confira a seção Atualização em segundo plano no pacote home_widget.
No iOS, o widget da tela inicial também pode fazer uma solicitação de rede para atualizar a interface. Para controlar as condições ou a frequência dessa solicitação, use a linha do tempo. Para saber mais sobre como usar a linha do tempo, consulte a documentação da Apple "Manter um widget atualizado" .