1. Introdução
O que são widgets?
Para desenvolvedores do Flutter, a definição comum de widget se refere a componentes de IU criados usando o framework do Flutter. No contexto deste codelab, um widget se refere a uma miniversão de um app que oferece uma visualização das informações sem abrir o app. No Android, os widgets ficam na tela inicial. No iOS, elas podem ser adicionadas à tela inicial, à tela de bloqueio ou à visualização "Hoje".
Qual a complexidade 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. Tanto o Android quanto o iOS limitam os componentes e recursos da interface que podem ser usados.
Criar a interface para widgets
Devido a essas limitações na interface, não é possível desenhar diretamente a interface de um widget da tela inicial usando o framework do Flutter. Em vez disso, você pode adicionar widgets criados com frameworks de plataforma como Jetpack Compose ou SwiftUI ao app Flutter. Este codelab discute exemplos de compartilhamento de recursos entre o app e os widgets para evitar reescrever uma interface complexa.
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:
- Mostrar dados do seu app do Flutter.
- Mostre texto usando recursos de fonte compartilhados do app Flutter.
- Mostre a imagem de um widget renderizado do Flutter.
Este app do Flutter inclui duas telas (ou rotas):
- A primeira mostra uma lista de matérias com títulos e descrições.
- A segunda 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 ambas as plataformas, você precisa do SDK do Flutter e de um ambiente de desenvolvimento integrado. Você pode usar seu ambiente de desenvolvimento integrado preferido para trabalhar com o Flutter. Pode ser o Visual Studio Code com as extensões do Flutter e do Dart Code (link em inglês), ou o Android Studio ou o IntelliJ com os plug-ins do Flutter e do Dart (links em inglês) instalados.
Para criar o widget da tela inicial do iOS:
- É possível executar este codelab em um dispositivo físico com iOS ou no simulador de iOS.
- Você precisa configurar um sistema macOS com o ambiente de desenvolvimento integrado do Xcode. Isso instala o compilador necessário para criar a versão para iOS do seu app.
Para criar o widget da tela inicial do Android:
- É possível executar este codelab em um dispositivo Android físico ou no Android Emulator.
- Você precisa configurar seu sistema de desenvolvimento com o Android Studio. Isso instala o compilador necessário para criar a versão para 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 do Flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
Depois de clonar o repositório, você pode encontrar 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.
Abrir 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 da plataforma nativa.
Como criar um widget básico da tela inicial do iOS
Adicionar uma extensão de aplicativo ao seu app do Flutter para iOS é semelhante a adicionar uma extensão de aplicativo a um app do SwiftUI ou UIKit:
- Execute
open ios/Runner.xcworkspace
em uma janela do 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 Abrir no Xcode. Isso abre o espaço de trabalho padrão do Xcode no seu projeto do Flutter. - Selecione File → Novo → Destino no menu. Isso adiciona um novo destino ao projeto.
- Uma lista de modelos será exibida. Selecione Extensão de widget.
- Digite "NewsWidgets" na caixa Nome do produto desse widget. Desmarque as caixas de seleção Include Live Activity e Include Configuration Intent.
Inspecionar o exemplo de código
Quando você adiciona um novo destino, o Xcode gera um código de amostra com base no modelo selecionado. Para mais informações sobre o código gerado e o WidgetKit, consulte a documentação da extensão de aplicativo da Apple.
Depurar e testar seu widget de amostra
- Primeiro, atualize a configuração do app do Flutter. Você precisa fazer isso ao adicionar novos pacotes no app Flutter e planejar executar um destino no projeto do Xcode. Para atualizar a configuração do app, execute o seguinte comando no diretório de apps do Flutter:
$ flutter build ios --config-only
- Clique em Executor para abrir uma lista de destinos. Selecione o destino do widget recém-criado, NewsWidgets, e clique em Executar. Execute o destino do widget do Xcode ao alterar o código do widget do iOS.
- O simulador ou a tela do dispositivo precisa exibir um widget básico da tela inicial. Caso não o veja, adicione-o à tela. Clique e mantenha pressionada a tela inicial e clique em + no canto superior esquerdo.
- Pesquise o nome do app. Neste codelab, pesquise "Widgets da tela inicial".
- Depois que você adicionar o widget da tela inicial, ele vai mostrar um texto simples com a hora certa.
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. Esse arquivo pode ser encontrado em android/build.gradle. Como alternativa, clique com o botão direito do mouse na pasta android do VSCode e selecione Abrir no 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 este diretório. Clique com o botão direito do mouse no diretório e selecione New -> Widget -> widget de app.
- O Android Studio mostra um novo formulário. Adicione informações básicas sobre seu widget da tela inicial, incluindo nome da classe, canal, tamanho e idioma de origem
Para este codelab, defina os seguintes valores:
- caixa Nome da classe para NewsWidget
- Menu suspenso Largura mínima (células) como 3
- Menu suspenso Altura mínima (células) como 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 | Mudança |
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 seu widget neste 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 seu widget de amostra
Agora, execute o aplicativo e veja 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 ou emulador Android mostra o widget padrão da tela inicial para Android.
4. Enviar dados do app do Flutter para o widget da tela inicial
Você pode personalizar o widget básico da tela inicial que você criou. Atualize o widget da tela inicial para exibir uma manchete e um resumo de uma notícia. A captura de tela abaixo mostra um exemplo do widget da tela inicial mostrando um título e um resumo.
Para transmitir dados entre o app e o widget da tela inicial, é necessário programar em Dart e código nativo. Esta seção divide esse processo em três partes:
- Como escrever código Dart no seu app Flutter que pode ser usado por Android e iOS
- Adicionar funcionalidade nativa do iOS
- Como adicionar funcionalidades nativas do Android
Usar os grupos de apps para iOS
Para compartilhar dados entre um app principal do iOS e uma extensão de widget, os dois destinos precisam pertencer ao mesmo grupo de apps. Para saber mais sobre os grupos de apps, consulte a documentação do grupo de apps da Apple.
Atualize o identificador do pacote:
No Xcode, acesse as configurações de destino. Na seção Como assinar e Capabilities, verifique se o identificador do pacote e da equipe 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 Runner (app pai) e o destino do widget.
Adicionar o código Dart
Tanto os apps iOS quanto os Android podem compartilhar dados com um app do Flutter de algumas maneiras diferentes.Para se comunicar com esses apps, use o armazenamento local da key/value
do dispositivo. O iOS chama essa loja de UserDefaults
, e o Android chama essa loja de SharedPreferences
. O pacote home_widget envolve essas APIs para simplificar o salvamento de dados em qualquer uma das plataformas e permite que os widgets da tela inicial extraiam dados atualizados.
Os dados do título e da descrição são provenientes 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 do título e da descrição
Para adicionar a funcionalidade de atualizar o widget da tela inicial no seu 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 seu 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 dos widgets da tela inicial podem ser recuperados e renderizados.
Modifique o flutuanteActionButton
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 exibir os dados do artigo
Para atualizar o widget da tela inicial para iOS, use o Xcode.
Abra o arquivo NewsWidgets.swift
no Xcode:
Configure o TimelineEntry
.
Substitua o 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
}
Esse struct NewsArticleEntry
define os dados de entrada que serão transmitidos ao widget da tela inicial quando atualizado. O tipo TimelineEntry
requer um parâmetro de data.Para saber mais sobre o protocolo TimelineEntry
, consulte a documentação da LineEntry da Apple (link em inglês).
Edite a View
que mostra o conteúdo.
Modifique o widget da tela inicial para exibir o título e a descrição da matéria em vez da data. Para exibir texto no SwiftUI, use a visualização Text
. Para empilhar visualizações 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 a atualização deve ser feita
Substitua o Provider
existente 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
placeholder
gera uma entrada de marcador quando o usuário visualiza o widget da tela inicial pela primeira vez.
- O método
getSnapshot
lê os dados dos padrões do usuário e gera a entrada para o horário atual. - O método
getTimeline
retorna entradas da linha do tempo. Isso é útil quando você tem horários previsíveis para atualizar seu conteúdo. Este codelab usa a função getSnapshot para receber o estado atual. O método.atEnd
instrui o widget da tela inicial a atualizar os dados após o período atual.
Comente em NewsWidgets_Previews
O uso de visualizações está fora do escopo deste codelab. Para saber mais sobre a visualização de widgets da tela inicial 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 da extensão no Xcode para executar o destino da extensão.
- Acesse a página de uma matéria no app.
- Clique no botão para atualizar o título. O widget da tela inicial também deve 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
. Ela define a estrutura e o layout do seu widget na tela inicial. Selecione Code no canto superior direito e substitua o conteúdo do 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 a manchete do artigo e outra para a descrição do artigo. Essas visualizações de texto também definem o estilo. Você vai voltar a ele 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 com o nome 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, em seguida, 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. Como usar fontes personalizadas do app Flutter no widget da tela inicial do iOS
Até agora, você configurou o widget da tela inicial para ler dados fornecidos pelo app Flutter. O app Flutter inclui uma fonte personalizada que você pode usar no widget da tela inicial. Você pode usar a fonte personalizada no widget da tela inicial do iOS. O uso de fontes personalizadas nos widgets da tela inicial não está disponível no Android.
Atualizar o código do iOS
O Flutter armazena os recursos no mainBundle de aplicativos iOS. É possível acessar os recursos desse pacote no código do widget da tela inicial.
Na estrutura NewsWidgetsEntryView do arquivo NewsWidgets.swift, faça as seguintes alterações
Crie uma função auxiliar para acessar o caminho até o 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 para seu 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 uma 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 agora usa a fonte personalizada para o título, conforme exibido na seguinte imagem:
6. Renderizar widgets do Flutter como uma imagem
Nesta seção, você vai exibir um gráfico do seu app Flutter como um widget da tela inicial.
Esse widget oferece um desafio maior do que o texto exibido na tela inicial. É muito mais fácil mostrar o gráfico do Flutter como uma imagem do que tentar recriá-lo usando componentes de interface nativos.
Programe o widget da tela inicial para renderizar o gráfico do Flutter como um arquivo PNG. Seu widget da tela inicial pode mostrar essa imagem.
Programar o código Dart
No Dart, adicione o método renderFlutterWidget
do pacote home_widget. Esse método recebe um widget, um nome de arquivo e uma chave. Ela 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 compilação retorna um CustomPainter que pinta o 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 ver o tamanho desse widget .
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 renderização
O _globalKey
contém o widget do Flutter que é renderizado para a imagem. Nesse caso, o widget do Flutter é o centro 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 "captura de tela" ao diretório de contêiner compartilhado. O método também salva o caminho completo da imagem como a chave de nome de 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
No iOS, atualize o código para receber o caminho do arquivo do armazenamento e exibir o arquivo como uma imagem usando o SwiftUI.
Abra o arquivo NewsWidgets.swift
para fazer as seguintes alterações:
Adicione filename
e displaySize
ao struct de 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)
}
Encontrar 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"
...
)
Criar 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 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 de NewsWidgetsEntryView para exibir 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 do 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 do Android funciona da mesma forma que o do iOS.
- Abra o arquivo
android/app/res/layout/news_widget.xml
. Ele contém os elementos de interface do widget da tela inicial. Substitua o conteúdo por este 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) exibe 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 por este 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
a partir dele. Se a imagem existir, o código Dart a substituirá no widget da tela inicial pela nova.
- Atualize o app e navegue até uma tela de 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 de tela inicial para seus apps do Flutter para iOS e Android.
Como criar links para conteúdo no seu app do Flutter
É possível direcionar o usuário a uma página específica no app, dependendo de onde ele clicar. Por exemplo, no app de notícias deste codelab, você pode querer que o usuário veja a matéria da manchete exibida.
Esse recurso está fora do escopo deste codelab. É possível encontrar exemplos de uso de um stream fornecido pelo pacote home_widget para identificar inicializações de apps nos widgets da tela inicial e enviar mensagens desse widget por meio do URL. Para saber mais, consulte a documentação sobre links diretos em docs.flutter.dev.
Como atualizar seu 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 seu 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, você também pode fazer com que o widget da tela inicial faça 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 Como manter um widget atualizado" na documentação do Google Cloud.