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 dell'interfaccia utente creati utilizzando il framework Flutter. Nel contesto di questo codelab, per widget si intende una versione mini di un'app che offre una visualizzazione delle informazioni dell'app senza aprirla. Su Android, i widget sono visibili sulla schermata Home. Su iOS, possono essere aggiunte 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, elementi grafici semplici 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 la UI per i widget

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

Cosa creerai

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

  • Mostra i dati dell'app Flutter.
  • Mostra il testo utilizzando gli asset di carattere condivisi dall'app Flutter.
  • Mostra l'immagine del widget Flutter sottoposto a rendering.

a36b7ba379151101.png

Questa app Flutter include due schermate (o percorsi):

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

.

9c02f8b62c1faa3a.png d97d44051304cae4.png

Cosa imparerai a fare

  • Come creare widget nella schermata Home su iOS e Android.
  • Come utilizzare il pacchetto home_widget per condividere 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. Potrebbe trattarsi di Visual Studio Code con le estensioni Dart Code e Flutter oppure 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. Verrà installato il compilatore necessario per creare la versione per 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 tuo sistema di sviluppo con Android Studio. Verrà installato il compilatore necessario per creare la versione Android della tua app.

Ottieni il codice di avvio

Scarica 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 nel codelab.

Apri l'app iniziale

Apri la directory flutter-codelabs/homescreen_codelab/step_03 nel tuo IDE preferito.

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 usando gli strumenti nativi della piattaforma.

Creare un widget di base della schermata Home di iOS

L'aggiunta di un'estensione per app all'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 da VSCode e seleziona Open in Xcode. Viene aperta l'area di lavoro Xcode predefinita nel tuo progetto Flutter.
  2. Seleziona File → Nuovo → Target dal menu. Viene così aggiunto un nuovo target al progetto.
  3. Viene visualizzato un elenco di modelli. Seleziona Estensione widget.
  4. Digita "NewsWidgets" nella casella Product Name (Nome prodotto) del widget. Deseleziona le caselle di controllo Includi attività dal vivo e Includi intent di configurazione.

Esaminare il codice campione

Quando aggiungi un nuovo target, Xcode genera un codice campione in base al modello selezionato. Per ulteriori informazioni sul codice generato e su WidgetKit, fai riferimento alla documentazione delle estensioni per app Apple.

Eseguire il debug e testare il widget di esempio

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

bbb519df1782881d.png

  1. Sul simulatore o sullo schermo del dispositivo dovrebbe essere visualizzato un widget della schermata Home di base. Se non la vedi, puoi aggiungerla allo schermo. Fai clic e tieni premuto sulla schermata Home, poi fai clic sul + nell'angolo in alto a sinistra.

18eff1cae152014d.png

  1. Cerca il nome dell'app. Per questo codelab, cerca "Widget schermata Home"

a0c00df87615493e.png

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

Creazione di 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 all'indirizzo android/build.gradle. In alternativa, fai clic con il tasto destro del mouse sulla cartella android da VSCode e seleziona Apri in Android Studio.
  2. Dopo aver creato il 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 pulsante destro del mouse sulla directory, quindi seleziona Nuovo -> Widget -> app.

f19d8b7f95ab884e.png

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

Per questo codelab, imposta i seguenti valori:

  • Casella Nome del corso a NewsWidget
  • Menu a discesa Larghezza minima (celle) su 3.
  • Menu a discesa Altezza minima (celle) su 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 destinatario che registra NewsWidget.

Crea

res/layout/news_widget.xml

Definisce l'interfaccia utente del widget della schermata Home.

Crea

res/xml/news_widget_info.xml

Definisce la configurazione del widget della schermata Home. Puoi regolare 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 durante questo codelab.

Eseguire il debug e testare il widget di esempio

Ora esegui l'applicazione e vedi il widget della schermata Home. Dopo aver creato l'app, vai alla schermata di selezione dell'applicazione sul tuo dispositivo Android e premi a lungo l'icona di questo progetto Flutter. Seleziona Widget dal menu popup.

dff7c9f9f85ef1c7.png

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

4. Invia 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. Il seguente screenshot mostra un esempio del widget della schermata Home con un titolo e un riepilogo.

acb90343a3e51b6d.png

Per trasferire i dati tra l'app e il widget della schermata Home, devi scrivere codice nativo e Dart. In questa sezione la procedura è suddivisa in tre parti:

  1. Scrittura di codice Dart nell'app Flutter utilizzabile sia da Android che da iOS
  2. Aggiunta di funzionalità iOS native
  3. Aggiunta delle funzionalità native di Android

Utilizzare Gruppi di app per iOS

Per condividere i dati tra un'app principale iOS e un'estensione widget, entrambe le destinazioni devono appartenere allo stesso gruppo di app. Per scoprire di più sui gruppi di app, consulta la documentazione relativa ai gruppi di app di Apple.

Aggiorna l'identificatore pacchetto:

In Xcode, vai alle impostazioni del target. Nella sezione Accesso e Funzionalità, controlla che l'identificatore del team e del bundle siano impostati.

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

Seleziona + Capacità -> Gruppi di app e aggiungi un nuovo gruppo. Ripeti l'operazione sia per la destinazione Runner (app principale) sia per la destinazione del widget.

135e1a8c4652dac.png

Aggiungi il codice Dart

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

707ae86f6650ac55.png

I dati relativi a titolo e 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,
  });
}

Aggiornare i valori del titolo e della descrizione

Per aggiungere la funzionalità di aggiornamento del widget della schermata Home dall'app Flutter, vai al file lib/home_screen.dart. Sostituisci i contenuti del file con il codice che segue. Sostituisci <YOUR APP GROUP> con l'identificatore del 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 nello spazio di archiviazione locale del dispositivo. La chiave headline_title contiene il valore di 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 il valore floatingActionButton

Richiama la funzione updateHeadline quando premi 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:

Configurare l'TimelineEntry.

Sostituisci lo struct 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
}

Questo struct NewsArticleEntry definisce i dati in arrivo da passare al widget della schermata Home quando viene aggiornato. Il tipo TimelineEntry richiede un parametro date.Per saperne di più sul protocollo TimelineEntry, consulta la documentazione di Spostamenti di Apple.

Modifica il View che mostra i contenuti

Modifica il widget della schermata Home in modo da visualizzare il titolo e la descrizione dell'articolo al posto della data. Per visualizzare il testo in SwiftUI, usa la visualizzazione Text. Per sovrapporre le visualizzazioni in SwiftUI, usa la vista VStack.

Sostituisci la vista 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 campo Provider esistente con il seguente codice. Quindi, sostituisci <IL TUO GRUPPO DI APP> con l'identificatore del gruppo di app:

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 TimelineProvider. Provider prevede 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 relativa all'ora corrente.
  2. Il metodo getTimeline restituisce voci della sequenza temporale. Ciò è utile quando si hanno tempi prevedibili per aggiornare i 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 una volta trascorso il tempo corrente.

Commenta NewsWidgets_Previews

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

Salva tutti i file ed esegui di nuovo la destinazione del widget e dell'app.

Esegui di nuovo gli obiettivi 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.

Aggiorna il codice Android

Aggiungi il file XML del widget nella 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 nella 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. Anche queste visualizzazioni di testo definiscono lo stile. Tornerai a questo file durante il codelab.

Aggiornare la funzionalità di NewsWidget

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

La classe NewsWidget contiene tre metodi della sua 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 riceve i valori più recenti dallo spazio di archiviazione locale utilizzando il metodo the widgetData.getString(), quindi chiama setTextViewText per modificare il testo visualizzato nel widget della schermata Home.

Testa gli aggiornamenti

Testa l'app per assicurarti che i widget della schermata Home si aggiornino con nuovi dati. Per aggiornare i dati, usa 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. Usare 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 usare nel widget della schermata Home. Puoi usare il carattere personalizzato nel widget della schermata Home di iOS. L'utilizzo dei caratteri personalizzati nei widget della schermata Home non è disponibile su Android.

Aggiorna il codice iOS

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

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

Il widget della schermata Home ora utilizza il carattere personalizzato per il titolo visualizzato nell'immagine seguente:

93f8b9d767aacfb2.png

6. Rendering dei widget Flutter come immagine

In questa sezione, vedrai un grafico dell'app Flutter sotto forma di widget nella schermata Home.

Questo widget presenta una sfida maggiore rispetto al testo visualizzato sulla schermata Home. È molto più facile visualizzare il grafico Flutter come immagine anziché provare a ricrearlo utilizzando i componenti dell'interfaccia utente nativi.

Codifica il widget della schermata Home per eseguire il rendering del grafico Flutter come file PNG. Nel widget della schermata Home l'immagine può essere visualizzata.

Scrivi il codice Dart

Sul lato Dart, aggiungi il metodo renderFlutterWidget dal pacchetto home_widget. Questo metodo richiede 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 l'intero percorso del file come stringa nello spazio di archiviazione locale del dispositivo. Ciò consente al widget della schermata Home di 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 metodo di compilazione restituisce un CustomPainter che visualizza il 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!),
        ],
      ),
    );
  }
}

In questo esempio vengono apportate tre modifiche alla classe _ArticleScreenState.

Crea una GlobalKey

GlobalKey ottiene il contesto del widget specifico, necessario per conoscere 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 visualizzato il widget Flutter.

lib/article_screen.dart

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

Aggiunge la chiave al widget per il rendering

_globalKey contiene il widget Flutter di cui viene eseguito il rendering dell'immagine. In questo caso, il widget Flutter è il centro 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 sul floatingActionButton. Il metodo salva il file PNG risultante come "screenshot" alla directory del container 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 in modo da recuperare 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 allo struct NewsArticleEntry

La proprietà filename contiene la stringa che rappresenta il percorso del file immagine. La proprietà displaySize include 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)
    }

Recupera il nome file da userDefaults in getSnapshot

In questo modo la variabile filename viene impostata sul valore filename nello spazio di archiviazione userDefaults quando il widget della schermata Home si aggiorna.

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 vista ChartImage crea un'immagine dai contenuti del file generato sul lato Dart. Qui puoi impostare le dimensioni al 50% dell'inquadratura.

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
}

Testa le modifiche

Per testare le modifiche, esegui nuovamente sia il target dell'app Flutter (Runner) sia il target dell'estensione da Xcode. Per vedere l'immagine, vai a una delle pagine dell'articolo nell'app e premi il pulsante per aggiornare il widget della schermata Home.

33bdfe2cce908c48.png

Aggiornare il codice Android

Il codice per Android funziona come il codice per iOS.

  1. Apri il file android/app/res/layout/news_widget.xml. Contiene gli elementi UI 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 il momento) visualizza un'icona generica a forma di stella. Sostituisci questa icona a forma di stella con l'immagine salvata nel codice Dart.

  1. Apri il file NewsWidget.kt. Sostituiscine 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 nello spazio di archiviazione locale con la chiave filename. Inoltre, recupera 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 visualizza il grafico.

7. Passaggi successivi

Complimenti!

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

Collegamento a contenuti nell'app Flutter

Potresti voler indirizzare l'utente a una pagina specifica dell'app, a seconda di dove fa clic. Ad esempio, nell'app di notizie di questo codelab, potresti volere che l'utente veda l'articolo per il 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 i lanci 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 link diretti su docs.flutter.dev.

Aggiornare il widget in background

In questo codelab, hai attivato un aggiornamento del widget della schermata Home utilizzando un pulsante. Sebbene questo sia ragionevole per i test, nel codice di produzione potresti voler aggiornare il widget della schermata Home in background nell'app. Puoi utilizzare il plug-in workmanager per creare attività in background al fine di aggiornare le risorse necessarie al widget della schermata Home. Per saperne di più, dai un'occhiata alla sezione Aggiornamento in background nel pacchetto home_widget.

Per iOS, puoi anche chiedere al widget della schermata Home di effettuare una richiesta di rete per aggiornare la sua UI. Per controllare le condizioni o la frequenza della richiesta, utilizza la sequenza temporale. Per ulteriori informazioni sull'utilizzo di Spostamenti, consulta la sezione "Mantenere aggiornato un widget" di Apple. documentazione.

Per approfondire