Aggiungere un widget della schermata Home all'app Flutter

1. Introduzione

Che cosa sono i widget?

Per gli sviluppatori Flutter, la definizione comune di widget si riferisce ai componenti UI creati utilizzando il framework Flutter. Nel contesto di questo codelab, un widget si riferisce a una mini versione di un'app che fornisce una visualizzazione delle informazioni dell'app senza aprirla. Su Android, i widget si trovano nella schermata Home. Su iOS, possono essere aggiunti alla schermata Home, alla schermata di blocco o alla visualizzazione Oggi.

f0027e8a7d0237e0.png b991e79ea72c8b65.png

Quanto può essere complesso un widget?

La maggior parte dei widget della schermata Home è semplice. Potrebbero contenere testo di base, grafica semplice o, su Android, controlli di base. Sia Android che iOS limitano i componenti e le funzionalità dell'interfaccia utente che puoi utilizzare.

819b9fffd700e571.png 92d62ccfd17d770d.png

Creare l'interfaccia utente per i widget

A causa di queste limitazioni della UI, non puoi disegnare direttamente la UI di un widget della schermata Home utilizzando il framework Flutter. Puoi invece aggiungere widget creati con framework della piattaforma come Jetpack Compose o SwiftUI alla tua app Flutter. Questo codelab illustra esempi di condivisione di risorse tra l'app e i widget per evitare di riscrivere UI complesse.

Cosa creerai

In questo codelab, creerai widget della schermata Home sia su Android che su iOS per una semplice app Flutter, utilizzando il pacchetto home_widget, che consente agli utenti di leggere articoli. I widget:

  • Mostra i dati della tua app Flutter.
  • Visualizza il testo utilizzando gli asset dei caratteri condivisi dall'app Flutter.
  • Mostra un'immagine di un widget Flutter sottoposto a rendering.

a36b7ba379151101.png

Questa app Flutter include due schermate (o route):

  • Il primo mostra un elenco di articoli di notizie con titoli e descrizioni.
  • La seconda mostra l'articolo completo con un grafico creato utilizzando CustomPaint.

.

9c02f8b62c1faa3a.png d97d44051304cae4.png

Cosa imparerai

  • Come creare widget della schermata Home su iOS e Android.
  • Come utilizzare il pacchetto home_widget per condividere i dati tra il widget della schermata Home e l'app Flutter.
  • Come ridurre la quantità di codice da riscrivere.
  • Come aggiornare il widget della schermata Home dall'app Flutter.

2. Configurazione dell'ambiente di sviluppo

Per entrambe le piattaforme, sono necessari l'SDK Flutter e un IDE. Puoi utilizzare il tuo IDE preferito per lavorare con Flutter. Può trattarsi di Visual Studio Code con le estensioni Dart Code e Flutter oppure di Android Studio o IntelliJ con i plug-in Flutter e Dart installati.

Per creare il widget della schermata Home di iOS:

  • Puoi eseguire questo codelab su un dispositivo iOS fisico o sul simulatore iOS.
  • Devi configurare un sistema macOS con l'IDE Xcode. Viene installato il compilatore necessario per creare la versione iOS della tua app.

Per creare il widget della schermata Home di Android:

  • Puoi eseguire questo codelab su un dispositivo Android fisico o sull'emulatore Android.
  • Devi configurare il sistema di sviluppo con Android Studio. Viene installato il compilatore necessario per creare la versione Android dell'app.

Ottenere il codice di avvio

Scaricare la versione iniziale del progetto da GitHub

Dalla riga di comando, clona il repository GitHub in una directory flutter-codelabs:

$ git clone https://github.com/flutter/codelabs.git flutter-codelabs

Dopo aver clonato il repository, puoi trovare il codice per questo codelab nella directory flutter-codelabs/homescreen_codelab. Questa directory contiene il codice del progetto completato per ogni passaggio del codelab.

Apri l'app di avvio

Apri la directory flutter-codelabs/homescreen_codelab/step_03 nell'IDE che preferisci.

Installare i pacchetti

Tutti i pacchetti richiesti sono stati aggiunti al file pubspec.yaml del progetto. Per recuperare le dipendenze del progetto, esegui questo comando:

$ flutter pub get

3. Aggiungere un widget di base alla schermata Home

Innanzitutto, aggiungi il widget della schermata Home utilizzando gli strumenti della piattaforma nativa.

Creare un widget di base per la schermata Home di iOS

L'aggiunta di un'estensione per app alla tua app Flutter per iOS è simile all'aggiunta di un'estensione per app a un'app SwiftUI o UIKit:

  1. Esegui open ios/Runner.xcworkspace in una finestra del terminale dalla directory del progetto Flutter. In alternativa, fai clic con il tasto destro del mouse sulla cartella ios in VSCode e seleziona Open in Xcode (Apri in Xcode). Si aprirà il workspace Xcode predefinito nel progetto Flutter.
  2. Seleziona File → Nuovo → Target dal menu. In questo modo, viene aggiunto un nuovo target al progetto.
  3. Viene visualizzato un elenco di modelli. Seleziona Estensione widget.
  4. Digita "NewsWidgets" nella casella Nome prodotto per questo widget. Deseleziona le caselle di controllo Includi attività live e Includi intent di configurazione.

Esaminare il codice campione

Quando aggiungi un nuovo target, Xcode genera un codice di esempio in base al modello selezionato. Per saperne di più sul codice generato e su WidgetKit, consulta la documentazione di Apple sulle estensioni delle app .

Eseguire il debug e testare il widget di esempio

  1. Innanzitutto, aggiorna la configurazione dell'app Flutter. Devi farlo quando aggiungi nuovi pacchetti nella tua app Flutter e prevedi di eseguire un target nel progetto da Xcode. Per aggiornare la configurazione dell'app, esegui questo comando nella directory dell'app Flutter:
$ flutter build ios --config-only
  1. Fai clic su Runner per visualizzare un elenco di target. Seleziona il target del widget appena creato, NewsWidgets, e fai clic su Esegui. Esegui il target del widget da Xcode quando modifichi il codice del widget iOS.

bbb519df1782881d.png

  1. Il simulatore o lo schermo del dispositivo dovrebbe mostrare un widget di base della schermata Home. Se non lo vedi, puoi aggiungerlo alla schermata. Fai clic e tieni premuto sulla schermata Home, poi fai clic su + nell'angolo in alto a sinistra.

18eff1cae152014d.png

  1. Cerca il nome dell'app. Per questo codelab, cerca "Homescreen Widgets" (Widget della schermata Home).

a0c00df87615493e.png

  1. Una volta aggiunto il widget della schermata Home, dovrebbe essere visualizzato un testo semplice che indica l'ora.

Creare un widget Android di base

  1. Per aggiungere un widget della schermata Home in Android, apri il file di build del progetto in Android Studio. Puoi trovare questo file in android/build.gradle. In alternativa, fai clic con il tasto destro del mouse sulla cartella android in VSCode e seleziona Open in Android Studio (Apri in Android Studio).
  2. Dopo la creazione del progetto, individua la directory dell'app nell'angolo in alto a sinistra. Aggiungi il nuovo widget della schermata Home a questa directory. Fai clic con il tasto destro del mouse sulla directory, seleziona Nuovo > Widget > Widget app.

f19d8b7f95ab884e.png

  1. Android Studio mostra un nuovo modulo. Aggiungi le informazioni di base sul widget della schermata Home, tra cui nome della classe, posizionamento, dimensioni e lingua di origine.

Per questo codelab, imposta i seguenti valori:

  • Casella Class Name (Nome classe) in NewsWidget
  • Larghezza minima (celle) a 3
  • Altezza minima (celle) a 3

Esaminare il codice campione

Quando invii il modulo, Android Studio crea e aggiorna diversi file. Le modifiche pertinenti per questo codelab sono elencate nella tabella seguente

Azione

File di destinazione

Cambia

Aggiorna

AndroidManifest.xml

Aggiunge un nuovo ricevitore che registra NewsWidget.

Crea

res/layout/news_widget.xml

Definisce la UI del widget della schermata Home.

Crea

res/xml/news_widget_info.xml

Definisce la configurazione del widget della schermata Home. Puoi modificare le dimensioni o il nome del widget in questo file.

Crea

java/com/example/homescreen_widgets/NewsWidget.kt

Contiene il codice Kotlin per aggiungere funzionalità al widget della schermata Home.

Puoi trovare maggiori dettagli su questi file in questo codelab.

Eseguire il debug e testare il widget di esempio

Ora esegui l'applicazione e visualizza il widget della schermata Home. Dopo aver creato l'app, vai alla schermata di selezione delle applicazioni del tuo dispositivo Android e tieni premuta l'icona di questo progetto Flutter. Seleziona Widget dal menu popup.

dff7c9f9f85ef1c7.png

Il dispositivo Android o l'emulatore mostra il widget della schermata Home predefinito per Android.

4. Inviare dati dall'app Flutter al widget della schermata Home

Puoi personalizzare il widget di base della schermata Home che hai creato. Aggiorna il widget della schermata Home per visualizzare un titolo e un riepilogo di un articolo di notizie. Il seguente screenshot mostra un esempio del widget della schermata Home che visualizza un titolo e un riepilogo.

acb90343a3e51b6d.png

Per trasferire dati tra l'app e il widget della schermata Home, devi scrivere codice Dart e nativo. Questa sezione suddivide il processo in tre parti:

  1. Scrivere codice Dart nell'app Flutter che può essere utilizzato sia da Android che da iOS
  2. Aggiungere funzionalità iOS native
  3. Aggiungere funzionalità Android native

Utilizzo dei gruppi di app per iOS

Per condividere dati tra un'app principale iOS e un'estensione widget, entrambi i target devono appartenere allo stesso gruppo di app. Per saperne di più sui gruppi di app, consulta la documentazione di Apple sui gruppi di app.

Aggiorna l'identificatore bundle:

In Xcode, vai alle impostazioni del target. Nella scheda Signing & Capabilities (Firma e funzionalità), verifica che siano impostati il team e l'identificatore bundle.

Aggiungi il gruppo di app sia al target Runner sia al target NewsWidgetExtension in Xcode:

Seleziona + Funzionalità -> Gruppi di app e aggiungi un nuovo gruppo di app. Ripeti l'operazione sia per il target Runner (app principale) sia per il target widget.

135e1a8c4652dac.png

Aggiungere il codice Dart

Le app iOS e Android possono condividere i dati con un'app Flutter in diversi modi.Per comunicare con queste app, utilizza l'archivio locale key/value del dispositivo. iOS chiama questo archivio UserDefaults, mentre Android lo chiama SharedPreferences. Il pacchetto home_widget esegue il wrapping di queste API per semplificare il salvataggio dei dati su entrambe le piattaforme e consente ai widget della schermata Home di estrarre i dati aggiornati.

707ae86f6650ac55.png

I dati del titolo e della descrizione provengono dal file news_data.dart. Questo file contiene dati fittizi e una classe di dati 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,
  });
}

Aggiorna i valori del titolo e della descrizione

Per aggiungere la funzionalità di aggiornamento del widget della schermata Home dalla tua app Flutter, vai al file lib/home_screen.dart. Sostituisci i contenuti del file con il seguente codice. Poi, sostituisci <YOUR APP GROUP> con l'identificatore del tuo gruppo di app.

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);
                    },
                  ),
                );
              },
            );
          },
        ));
  }
}

La funzione updateHeadline salva le coppie chiave/valore nella memoria locale del dispositivo. La chiave headline_title contiene il valore newHeadline.title. La chiave headline_description contiene il valore di newHeadline.description. La funzione comunica inoltre alla piattaforma nativa che è possibile recuperare e visualizzare nuovi dati per i widget della schermata Home.

Modificare floatingActionButton

Chiama la funzione updateHeadline quando viene premuto floatingActionButton come mostrato:

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'),
      ),
...

Con questa modifica, quando un utente preme il pulsante Aggiorna titolo dalla pagina di un articolo, i dettagli del widget della schermata Home vengono aggiornati.

Aggiorna il codice iOS per visualizzare i dati dell'articolo

Per aggiornare il widget della schermata Home per iOS, utilizza Xcode.

Apri il file NewsWidgets.swift in Xcode:

Configura TimelineEntry.

Sostituisci la struttura SimpleEntry con il seguente codice:

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
}

Questa struttura NewsArticleEntry definisce i dati in arrivo da passare al widget della schermata Home quando viene aggiornato. Il tipo TimelineEntry richiede un parametro data.Per scoprire di più sul protocollo TimelineEntry, consulta la documentazione di Apple su TimelineEntry.

Modifica il View che mostra i contenuti

Modifica il widget della schermata Home per visualizzare il titolo e la descrizione dell'articolo di notizie anziché la data. Per visualizzare il testo in SwiftUI, utilizza la visualizzazione Text. Per impilare le visualizzazioni una sopra l'altra in SwiftUI, utilizza la visualizzazione VStack.

Sostituisci la visualizzazione NewsWidgetEntryView generata con il seguente codice:

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)
      }
    }
}

Modifica il fornitore per indicare al widget della schermata Home quando e come eseguire l'aggiornamento

Sostituisci il codice Provider esistente con il seguente. Poi, sostituisci l'identificatore del gruppo di app a <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)
              }
    }
}

Provider nel codice precedente è conforme a un TimelineProvider. Provider ha tre metodi diversi:

  1. Il metodo placeholder genera una voce segnaposto quando l'utente visualizza per la prima volta l'anteprima del widget della schermata Home.

45a0f64240c12efe.png

  1. Il metodo getSnapshot legge i dati dalle impostazioni predefinite dell'utente e genera la voce per l'ora corrente.
  2. Il metodo getTimeline restituisce le voci della cronologia. Questo è utile quando hai punti temporali prevedibili per aggiornare i tuoi contenuti. Questo codelab utilizza la funzione getSnapshot per ottenere lo stato attuale. Il metodo.atEnd indica al widget della schermata Home di aggiornare i dati dopo che è trascorsa l'ora corrente.

Commenta NewsWidgets_Previews

L'utilizzo delle anteprime non rientra nell'ambito di questo codelab. Per ulteriori dettagli sull'anteprima dei widget della schermata Home di SwiftUI, consulta la documentazione di Apple sul debug dei widget.

Salva tutti i file ed esegui di nuovo il target dell'app e del widget.

Esegui di nuovo gli intent per verificare che l'app e il widget della schermata Home funzionino.

  1. Seleziona lo schema dell'app in Xcode per eseguire il target dell'app.
  2. Seleziona lo schema dell'estensione in Xcode per eseguire il target dell'estensione.
  3. Vai alla pagina di un articolo nell'app.
  4. Fai clic sul pulsante per aggiornare il titolo. Anche il widget della schermata Home dovrebbe aggiornare il titolo.

Aggiornare il codice Android

Aggiungi il file XML del widget della schermata Home.

In Android Studio, aggiorna i file generati nel passaggio precedente.Apri il file res/layout/news_widget.xml. Definisce la struttura e il layout del widget della schermata Home. Seleziona Codice nell'angolo in alto a destra e sostituisci i contenuti del file con il seguente codice:

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>

Questo XML definisce due visualizzazioni di testo, una per il titolo dell'articolo e l'altra per la descrizione dell'articolo. Queste visualizzazioni di testo definiscono anche lo stile. Tornerai a questo file durante tutto il codelab.

Aggiornamento della funzionalità del widget Notizie

Apri il file di codice sorgente Kotlin NewsWidget.kt. Questo file contiene una classe generata denominata NewsWidget che estende la classe AppWidgetProvider.

La classe NewsWidget contiene tre metodi della superclasse. Modificherai il metodo onUpdate. Android chiama questo metodo per i widget a intervalli fissi.

Sostituisci i contenuti del file NewsWidget.kt con il seguente codice:

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)
        }
    }
}

Ora, quando viene chiamato onUpdate, Android recupera i valori più recenti dall'archivio locale utilizzando il metodo the widgetData.getString(), quindi chiama setTextViewText per modificare il testo visualizzato nel widget della schermata Home.

Testare gli aggiornamenti

Testa l'app per assicurarti che i widget della schermata Home si aggiornino con i nuovi dati. Per aggiornare i dati, utilizza l'opzione Aggiorna schermata Home FloatingActionButton nelle pagine degli articoli. Il widget della schermata Home dovrebbe aggiornarsi con il titolo dell'articolo.

5ce1c9914b43ad79.png

5. Utilizzare i caratteri personalizzati dell'app Flutter nel widget della schermata Home di iOS

Finora, hai configurato il widget della schermata Home per leggere i dati forniti dall'app Flutter. L'app Flutter include un carattere personalizzato che potresti voler utilizzare nel widget della schermata Home. Puoi utilizzare il carattere personalizzato nel widget della schermata Home di iOS. L'utilizzo di caratteri personalizzati nei widget della schermata Home non è disponibile su Android.

Aggiorna il codice iOS

Flutter archivia i suoi asset nel mainBundle delle applicazioni iOS. Puoi accedere agli asset in questo bundle dal codice del widget della schermata Home.

Nella struct NewsWidgetsEntryView del file NewsWidgets.swift, apporta le seguenti modifiche

Crea una funzione helper per ottenere il percorso della directory degli asset 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
       }
   ...
}

Registra il carattere utilizzando l'URL del file del carattere personalizzato.

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)
   }
   ...
}

Aggiorna la visualizzazione Testo del titolo per utilizzare il carattere personalizzato.

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 esegui il widget della schermata Home, ora utilizza il carattere personalizzato per il titolo, come mostrato nell'immagine seguente:

93f8b9d767aacfb2.png

6. Rendering dei widget Flutter come immagine

In questa sezione, visualizzerai un grafico della tua app Flutter come widget della schermata Home.

Questo widget offre una sfida maggiore rispetto al testo visualizzato nella schermata Home. È molto più semplice visualizzare il grafico Flutter come immagine piuttosto che provare a ricrearlo utilizzando i componenti dell'interfaccia utente nativa.

Codifica il widget della schermata Home per eseguire il rendering del grafico Flutter come file PNG. Il widget della schermata Home può visualizzare questa immagine.

Scrivi il codice Dart

Sul lato Dart, aggiungi il metodo renderFlutterWidget dal pacchetto home_widget. Questo metodo accetta un widget, un nome file e una chiave. Restituisce un'immagine del widget Flutter e la salva in un contenitore condiviso. Fornisci il nome dell'immagine nel codice e assicurati che il widget della schermata Home possa accedere al contenitore. key salva il percorso completo del file come stringa nello spazio di archiviazione locale del dispositivo. In questo modo, il widget della schermata Home può trovare il file se il nome cambia nel codice Dart.

Per questo codelab, la classe LineChart nel file lib/article_screen.dart rappresenta il grafico. Il suo metodo di creazione restituisce un CustomPainter che disegna questo grafico sullo schermo.

Per implementare questa funzionalità, apri il file lib/article_screen.dart. Importa il pacchetto home_widget. Poi, sostituisci il codice nella classe _ArticleScreenState con il seguente codice:

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!),
        ],
      ),
    );
  }
}

Questo esempio apporta tre modifiche alla classe _ArticleScreenState.

Crea una GlobalKey

GlobalKey ottiene il contesto del widget specifico, necessario per ottenere le dimensioni del widget .

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   // New: add this GlobalKey
   final _globalKey = GlobalKey();
   ...
}

Aggiunge imagePath

La proprietà imagePath memorizza la posizione dell'immagine in cui viene eseguito il rendering del widget Flutter.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   // New: add this imagePath
   String? imagePath;
   ...
}

Aggiunge la chiave al widget da visualizzare

_globalKey contiene il widget Flutter sottoposto a rendering nell'immagine. In questo caso, il widget Flutter è Center, che contiene LineChart.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. Salva il widget come immagine

Il metodo renderFlutterWidget viene chiamato quando l'utente fa clic su floatingActionButton. Il metodo salva il file PNG risultante come "screenshot" nella directory del contenitore condiviso. Il metodo salva anche il percorso completo dell'immagine come chiave del nome file nello spazio di archiviazione del 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);
  },
   ...
}

Aggiornare il codice iOS

Per iOS, aggiorna il codice per ottenere il percorso del file dallo spazio di archiviazione e visualizzare il file come immagine utilizzando SwiftUI.

Apri il file NewsWidgets.swift per apportare le seguenti modifiche:

Aggiungi filename e displaySize a NewsArticleEntry struct

La proprietà filename contiene la stringa che rappresenta il percorso del file immagine. La proprietà displaySize contiene le dimensioni del widget della schermata Home sul dispositivo dell'utente. Le dimensioni del widget della schermata Home derivano da context.

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

   // New: add the filename and displaySize.
   let filename: String
   let displaySize: CGSize
}

Aggiorna la funzione placeholder

Includi un segnaposto 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)
    }

Ottieni il nome file da userDefaults in getSnapshot

In questo modo, la variabile filename viene impostata sul valore filename nell'archivio userDefaults quando il widget della schermata Home viene aggiornato.

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"
   ...
)

Crea ChartImage che visualizza l'immagine da un percorso

La visualizzazione ChartImage crea un'immagine dai contenuti del file generato sul lato Dart. Qui imposti le dimensioni al 50% del 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())
    }
   ...
}

Utilizza ChartImage nel corpo di NewsWidgetsEntryView

Aggiungi la visualizzazione ChartImage al corpo di NewsWidgetsEntryView per visualizzare ChartImage nel widget della schermata Home.

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
}

Testare le modifiche

Per testare le modifiche, esegui di nuovo sia la destinazione dell'app Flutter (Runner) sia la destinazione dell'estensione da Xcode. Per visualizzare l'immagine, vai a una delle pagine degli articoli nell'app e premi il pulsante per aggiornare il widget della schermata Home.

33bdfe2cce908c48.png

Aggiornare il codice Android

Il codice Android funziona come il codice iOS.

  1. Apri il file android/app/res/layout/news_widget.xml. Contiene gli elementi dell'interfaccia utente del widget della schermata Home. Sostituisci i contenuti con il seguente codice:

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>

Questo nuovo codice aggiunge un'immagine al widget della schermata Home, che (per ora) mostra un'icona a forma di stella generica. Sostituisci questa icona a forma di stella con l'immagine che hai salvato nel codice Dart.

  1. Apri il file NewsWidget.kt. Sostituisci i contenuti con il seguente codice:

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)
        }
    }
}

Questo codice Dart salva uno screenshot nella memoria locale con la chiave filename. Recupera anche il percorso completo dell'immagine e crea un oggetto File. Se l'immagine esiste, il codice Dart sostituisce l'immagine nel widget della schermata Home con la nuova immagine.

  1. Ricarica l'app e vai alla schermata di un articolo. Premi Aggiorna schermata Home. Il widget della schermata Home mostra il grafico.

7. Passaggi successivi

Complimenti!

Congratulazioni, sei riuscito a creare widget della schermata Home per le tue app Flutter per iOS e Android.

Collegamento a contenuti nella tua app Flutter

A seconda di dove fa clic l'utente, potresti volerlo indirizzare a una pagina specifica della tua app. Ad esempio, nell'app di notizie di questo codelab, potresti voler mostrare all'utente l'articolo di notizie relativo al titolo visualizzato.

Questa funzionalità non rientra nell'ambito di questo codelab. Puoi trovare esempi di utilizzo di uno stream fornito dal pacchetto home_widget per identificare gli avvii di app dai widget della schermata Home e inviare messaggi dal widget della schermata Home tramite l'URL. Per saperne di più, consulta la documentazione sui deep link su docs.flutter.dev.

Aggiornamento del widget in background

In questo codelab, hai attivato un aggiornamento del widget della schermata Home utilizzando un pulsante. Anche se questo è ragionevole per i test, nel codice di produzione potresti voler che la tua app aggiorni il widget della schermata Home in background. Puoi utilizzare il plug-in workmanager per creare attività in background per aggiornare le risorse necessarie al widget della schermata Home. Per saperne di più, consulta la sezione Aggiornamento in background nel pacchetto home_widget.

Per iOS, puoi anche fare in modo che il widget della schermata Home invii una richiesta di rete per aggiornare la sua UI. Per controllare le condizioni o la frequenza di questa richiesta, utilizza la cronologia. Per scoprire di più sull'utilizzo della sequenza temporale, consulta la documentazione di Apple "Mantenere aggiornato un widget" .

Per approfondire