Startbildschirm-Widget zur Flutter-App hinzufügen

1. Einführung

Was sind Widgets?

Für Flutter-Entwickler bezieht sich die allgemeine Definition von Widget auf UI-Komponenten, die mit dem Flutter-Framework erstellt wurden. Im Kontext dieses Codelabs bezieht sich ein Widget auf eine Miniversion einer App, die einen Einblick in die Informationen der App bietet, ohne die App zu öffnen. Auf Android-Geräten befinden sich Widgets auf dem Startbildschirm. Auf iOS-Geräten können sie dem Start-, Sperrbildschirm oder der Ansicht „Heute“ hinzugefügt werden.

f0027e8a7d0237e0.png b991e79ea72c8b65.png

Wie komplex kann ein Widget sein?

Die meisten Startbildschirm-Widgets sind einfach aufgebaut. Sie können einfachen Text, einfache Grafiken oder unter Android grundlegende Steuerelemente enthalten. Sowohl für Android als auch für iOS gibt es Einschränkungen, welche UI-Komponenten und ‐Funktionen Sie verwenden können.

819b9fffd700e571.png 92d62ccfd17d770d.png

Benutzeroberfläche für Widgets erstellen

Aufgrund dieser Einschränkungen der Benutzeroberfläche können Sie die Benutzeroberfläche eines Startbildschirm-Widgets nicht direkt mit dem Flutter-Framework zeichnen. Stattdessen können Sie Ihrer Flutter-App Widgets hinzufügen, die mit Plattform-Frameworks wie Jetpack Compose oder SwiftUI erstellt wurden. In diesem Codelab werden Beispiele für die gemeinsame Nutzung von Ressourcen zwischen Ihrer App und den Widgets erläutert, um das Umschreiben komplexer Benutzeroberflächen zu vermeiden.

Inhalt

In diesem Codelab erstellen Sie mit dem „home_widget“-Paket Startbildschirm-Widgets für Android und iOS für eine einfache Flutter-App, über die Nutzer Artikel lesen können. Für Ihre Widgets gilt Folgendes:

  • Daten aus der Flutter-App anzeigen.
  • Anzeigetext mithilfe von Schrift-Assets, die über die Flutter App freigegeben wurden
  • Zeigt das Bild eines gerenderten Flutter-Widgets an.

a36b7ba379151101.png

Diese Flutter-App enthält zwei Bildschirme (oder Routen):

  • Die erste enthält eine Liste von Nachrichtenartikeln mit Überschriften und Beschreibungen.
  • Das zweite Feld zeigt den vollständigen Artikel mit einem Diagramm an, das mit CustomPaint erstellt wurde.

.

9c02f8b62c1faa3a.png d97d44051304cae4.png

Lerninhalte

  • Startbildschirm-Widgets für iOS und Android erstellen
  • Informationen zum Teilen von Daten zwischen dem Startbildschirm-Widget und der Flutter-App mithilfe des Pakets „home_widget“
  • So reduzieren Sie die Menge an Code, den Sie neu schreiben müssen.
  • So aktualisieren Sie das Startbildschirm-Widget über die Flutter App.

2. Entwicklungsumgebung einrichten

Für beide Plattformen benötigen Sie das Flutter SDK und eine IDE. Sie können Ihre bevorzugte IDE verwenden, um mit Flutter zu arbeiten. Dies kann Visual Studio Code mit den Dart Code- und Flutter-Erweiterungen oder Android Studio oder IntelliJ mit den installierten Flutter- und Dart-Plug-ins sein.

So erstellst du das iOS-Startbildschirm-Widget:

  • Du kannst dieses Codelab auf einem physischen iOS-Gerät oder im iOS-Simulator ausführen.
  • Sie müssen ein macOS-System mit der Xcode IDE konfigurieren. Dadurch wird der Compiler installiert, der zum Erstellen der iOS-Version Ihrer App erforderlich ist.

So erstellst du das Android-Startbildschirm-Widget:

  • Du kannst dieses Codelab auf einem physischen Android-Gerät oder im Android-Emulator ausführen.
  • Sie müssen Ihr Entwicklungssystem mit Android Studio konfigurieren. Dadurch wird der Compiler installiert, der zum Erstellen der Android-Version Ihrer App erforderlich ist.

Startercode abrufen

Erste Version Ihres Projekts von GitHub herunterladen

Klonen Sie über die Befehlszeile das GitHub-Repository in ein Flutter-Codelabs-Verzeichnis:

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

Nachdem Sie das Repository geklont haben, finden Sie den Code für dieses Codelab im Verzeichnis „flutter-codelabs/homescreen_codelab“. Dieses Verzeichnis enthält fertigen Projektcode für jeden Schritt im Codelab.

Starter App öffnen

Öffnen Sie das Verzeichnis flutter-codelabs/homescreen_codelab/step_03 in Ihrer bevorzugten IDE.

Pakete installieren

Alle erforderlichen Pakete wurden der Datei pubspec.yaml des Projekts hinzugefügt. Führen Sie den folgenden Befehl aus, um die Projektabhängigkeiten abzurufen:

$ flutter pub get

3. Einfaches Startbildschirm-Widget hinzufügen

Fügen Sie zuerst mithilfe der nativen Plattformtools das Startbildschirm-Widget hinzu.

Einfaches Widget für den iOS-Startbildschirm erstellen

Das Hinzufügen einer App-Erweiterung zu Ihrer Flutter-iOS-App funktioniert ähnlich wie das Hinzufügen einer App-Erweiterung zu einer SwiftUI- oder UIKit-App:

  1. Führen Sie open ios/Runner.xcworkspace in einem Terminalfenster aus Ihrem Flutter-Projektverzeichnis aus. Alternativ können Sie in VSCode mit der rechten Maustaste auf den Ordner ios klicken und In Xcode öffnen auswählen. Dadurch wird der Xcode-Standardarbeitsbereich in Ihrem Flutter-Projekt geöffnet.
  2. Wählen Sie im Menü Datei → Neu → Ziel aus. Dadurch wird dem Projekt ein neues Ziel hinzugefügt.
  3. Eine Liste mit Vorlagen wird angezeigt. Wählen Sie Widget-Erweiterung aus.
  4. Geben Sie „NewsWidgets“ ein. in das Feld Produktname für dieses Widget ein. Deaktivieren Sie die Kästchen Include Live Activity (Live-Aktivität einschließen) und Include Configuration Intent.

Beispielcode prüfen

Wenn Sie ein neues Ziel hinzufügen, generiert Xcode Beispielcode basierend auf der ausgewählten Vorlage. Weitere Informationen zum generierten Code und zum WidgetKit finden Sie in der Dokumentation zur App-Erweiterung von Apple.

Fehler im Beispiel-Widget beheben und testen

  1. Aktualisieren Sie zuerst die Konfiguration Ihrer Flutter-App. Sie müssen dies tun, wenn Sie in Ihrer Flutter-App neue Pakete hinzufügen und planen, ein Ziel aus Xcode im Projekt auszuführen. Führen Sie den folgenden Befehl im Verzeichnis Ihrer Flutter-Anwendung aus, um die Konfiguration Ihrer Anwendung zu aktualisieren:
$ flutter build ios --config-only
  1. Klicken Sie auf Runner, um eine Liste mit Zielen aufzurufen. Wählen Sie „NewsWidgets“ das soeben erstellte Widget-Ziel aus und klicken Sie auf Ausführen. Führen Sie das Widget-Ziel über Xcode aus, wenn Sie den Code des iOS-Widgets ändern.

bbb519df1782881d.png

  1. Auf dem Simulator oder Gerätebildschirm sollte ein einfaches Startbildschirm-Widget angezeigt werden. Wenn Sie es nicht sehen, können Sie es dem Bildschirm hinzufügen. Klicken Sie auf den Startbildschirm, halten Sie die Maustaste gedrückt und klicken Sie links oben auf das +.

18eff1cae152014d.png

  1. Suchen Sie nach dem Namen der App. Suchen Sie für dieses Codelab nach „Homescreen Widgets“ (Startbildschirm-Widgets)

a0c00df87615493e.png

  1. Nachdem Sie das Startbildschirm-Widget hinzugefügt haben, sollte es einen einfachen Text mit der Uhrzeit anzeigen.

Einfaches Android-Widget erstellen

  1. Wenn Sie ein Startbildschirm-Widget in Android hinzufügen möchten, öffnen Sie die Build-Datei des Projekts in Android Studio. Sie finden diese Datei unter android/build.gradle. Alternativ können Sie in VSCode mit der rechten Maustaste auf den Ordner android klicken und In Android Studio öffnen auswählen.
  2. Suchen Sie nach der Erstellung des Projekts nach dem App-Verzeichnis in der oberen linken Ecke. Fügen Sie diesem Verzeichnis Ihr neues Startbildschirm-Widget hinzu. Klicken Sie mit der rechten Maustaste auf das Verzeichnis und wählen Sie New -> Widget -> App-Widget

f19d8b7f95ab884e.png

  1. Android Studio zeigt ein neues Formular an. Grundlegende Informationen zum Startbildschirm-Widget hinzufügen, einschließlich Klassenname, Platzierung, Größe und Ausgangssprache

Legen Sie für dieses Codelab die folgenden Werte fest:

  • Class Name (Kursname) zu NewsWidget hinzufügen.
  • Drop-down-Menü Minimale Breite (Zellen) auf 3
  • Drop-down-Menü Mindesthöhe (Zellen) auf 3

Beispielcode prüfen

Wenn Sie das Formular senden, werden in Android Studio mehrere Dateien erstellt und aktualisiert. Die für dieses Codelab relevanten Änderungen sind in der Tabelle unten aufgeführt

Aktion

Zieldatei

Ändern

Aktualisieren

AndroidManifest.xml

Fügt einen neuen Empfänger hinzu, der das NewsWidget registriert.

Erstellen

res/layout/news_widget.xml

Definiert die Benutzeroberfläche des Startbildschirm-Widgets.

Erstellen

res/xml/news_widget_info.xml

Definiert die Konfiguration des Startbildschirm-Widgets. Sie können die Abmessungen oder den Namen Ihres Widgets in dieser Datei anpassen.

Erstellen

java/com/example/homescreen_widgets/NewsWidget.kt

Enthält Ihren Kotlin-Code, um dem Startbildschirm-Widget Funktionen hinzuzufügen.

In diesem Codelab finden Sie weitere Informationen zu diesen Dateien.

Fehler im Beispiel-Widget beheben und testen

Führen Sie nun Ihre Anwendung aus und sehen Sie sich das Startbildschirm-Widget an. Gehen Sie nach dem Erstellen der App zum App-Auswahlbildschirm Ihres Android-Geräts und drücken Sie lange auf das Symbol für dieses Flutter-Projekt. Wählen Sie im Pop-up-Menü Widgets aus.

dff7c9f9f85ef1c7.png

Auf dem Android-Gerät oder im Emulator wird das standardmäßige Startbildschirm-Widget für Android angezeigt.

4. Daten von der Flutter App an das Startbildschirm-Widget senden

Sie können das einfache Startbildschirm-Widget, das Sie erstellt haben, anpassen. Aktualisieren Sie das Startbildschirm-Widget, um eine Schlagzeile und eine Zusammenfassung eines Nachrichtenartikels anzuzeigen. Der folgende Screenshot zeigt ein Beispiel für das Startbildschirm-Widget mit einer Überschrift und einer Zusammenfassung.

acb90343a3e51b6d.png

Um Daten zwischen der App und dem Startbildschirm-Widget zu übertragen, müssen Sie Dart-und nativen Code schreiben. In diesem Abschnitt wird dieser Prozess in drei Teile unterteilt:

  1. Dart-Code in der Flutter-App schreiben, den Android und iOS verwenden können
  2. Native iOS-Funktionen hinzufügen
  3. Native Android-Funktionen hinzufügen

iOS-App-Gruppen verwenden

Wenn Sie Daten zwischen einer übergeordneten iOS-App und einer Widget-Erweiterung teilen möchten, müssen beide Ziele derselben App-Gruppe angehören. Weitere Informationen zu App-Gruppen finden Sie in der Dokumentation zu App-Gruppen von Apple.

Aktualisiere deinen Paket-Identifikator:

Rufen Sie in Xcode die Einstellungen des Ziels auf. Klicken Sie im Dialogfeld Signieren und Funktionen können Sie überprüfen, ob Ihr Team und Ihre Bundle-ID festgelegt sind.

Fügen Sie die App-Gruppe dem Runner-Ziel und dem NewsWidgetExtension-Ziel in Xcode hinzu:

Wählen Sie + Funktion -> App-Gruppen und fügen Sie eine neue App-Gruppe hinzu. Wiederholen Sie diese Schritte sowohl für das Runner-Ziel (übergeordnete App) als auch für das Widget-Ziel.

135e1a8c4652dac.png

Dart-Code hinzufügen

Sowohl iOS- als auch Android-Apps können auf unterschiedliche Weise Daten mit einer Flutter-App teilen.Wenn Sie mit diesen Apps kommunizieren möchten, nutzen Sie den lokalen key/value-Shop des Geräts. Auf iOS-Geräten wird das Geschäft als UserDefaults und bei Android unter der Bezeichnung SharedPreferences aufgeführt. Das home_widget-Paket umfasst diese APIs, um das Speichern von Daten auf beiden Plattformen zu vereinfachen und den Startbildschirm-Widgets das Abrufen aktualisierter Daten zu ermöglichen.

707ae86f6650ac55.png

Die Daten für Anzeigentitel und Textzeile stammen aus der Datei news_data.dart. Diese Datei enthält simulierte Daten und eine NewsArticle-Datenklasse.

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

Werte für Anzeigentitel und Textzeile aktualisieren

Wenn Sie die Funktion zum Aktualisieren des Startbildschirm-Widgets über die Flutter App hinzufügen möchten, gehen Sie zur Datei lib/home_screen.dart. Ersetzen Sie den Inhalt der Datei durch den folgenden Code. Ersetzen Sie dann <YOUR APP GROUP> durch die ID Ihrer App-Gruppe.

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

Die Funktion updateHeadline speichert die Schlüssel/Wert-Paare im lokalen Speicher Ihres Geräts. Der Schlüssel headline_title enthält den Wert newHeadline.title. Der Schlüssel headline_description enthält den Wert von newHeadline.description. Die Funktion benachrichtigt auch die native Plattform, dass neue Daten für die Startbildschirm-Widgets abgerufen und gerendert werden können.

„floatingActionButton“ ändern

Rufen Sie die Funktion updateHeadline auf, wenn floatingActionButton gedrückt wird, wie hier gezeigt:

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

Mit dieser Änderung werden die Details des Startbildschirm-Widgets aktualisiert, wenn ein Nutzer auf einer Artikelseite auf die Schaltfläche Überschrift aktualisieren tippt.

iOS-Code zur Anzeige der Artikeldaten aktualisieren

Verwenden Sie Xcode, um das Startbildschirm-Widget für iOS zu aktualisieren.

Öffnen Sie die Datei NewsWidgets.swift in Xcode:

Konfigurieren Sie den TimelineEntry.

Ersetzen Sie die Struktur SimpleEntry durch den folgenden Code:

ios/NewsWidgets/NewsWidgets.swift

// The date and any data you want to pass into your app must conform to TimelineEntry
struct NewsArticleEntry: TimelineEntry {
    let date: Date
    let title: String
    let description:String
}

Diese NewsArticleEntry-Struktur definiert die eingehenden Daten, die bei der Aktualisierung an das Startbildschirm-Widget übergeben werden. Für den Typ TimelineEntry ist ein Datumsparameter erforderlich.Weitere Informationen zum TimelineEntry-Protokoll finden Sie in der TimelineEntry-Dokumentation von Apple.

Bearbeiten Sie die View, mit der der Inhalt angezeigt wird.

Passen Sie das Startbildschirm-Widget so an, dass anstelle des Datums die Schlagzeile und die Beschreibung des Nachrichtenartikels angezeigt werden. Verwenden Sie die Ansicht Text, um Text in SwiftUI anzuzeigen. Um Ansichten in SwiftUI übereinander zu stapeln, verwenden Sie die Ansicht VStack.

Ersetzen Sie die generierte NewsWidgetEntryView-Ansicht durch den folgenden Code:

ios/NewsWidgets/NewsWidgets.swift

//View that holds the contents of the widget
struct NewsWidgetsEntryView : View {
    var entry: Provider.Entry

    var body: some View {
      VStack {
        Text(entry.title)
        Text(entry.description)
      }
    }
}

Bearbeite den Anbieter, um dem Startbildschirm-Widget mitzuteilen, wann und wie das Update durchgeführt werden soll.

Ersetzen Sie die vorhandene Provider durch den folgenden Code. Ersetzen Sie dann <IHRE APP-GRUPPE> durch Ihre App-Gruppen-ID:

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

Die Provider im vorherigen Code entspricht einer TimelineProvider. Für Provider gibt es drei verschiedene Methoden:

  1. Die Methode placeholder generiert einen Platzhaltereintrag, wenn der Nutzer zum ersten Mal eine Vorschau des Startbildschirm-Widgets zeigt.

45a0f64240c12efe.png

  1. Die Methode getSnapshot liest die Daten aus den Standardeinstellungen der Nutzer und generiert den Eintrag für die aktuelle Zeit.
  2. Die Methode getTimeline gibt Zeitachseneinträge zurück. Das ist hilfreich, wenn du vorhersehbare Zeitpunkte für die Aktualisierung deiner Inhalte hast. In diesem Codelab wird mit der Funktion „getSnapshot“ der aktuelle Status abgerufen. Mit der Methode .atEnd wird das Startbildschirm-Widget angewiesen, die Daten nach Ablauf der aktuellen Zeit zu aktualisieren.

NewsWidgets_Previews auskommentieren

Die Verwendung von Vorschauen wird in diesem Codelab nicht behandelt. Weitere Informationen zur Vorschau von SwiftUI-Widgets auf dem Startbildschirm finden Sie in der Apple-Dokumentation zu Debugging-Widgets.

Speichern Sie alle Dateien und führen Sie die App und das Widget-Ziel noch einmal aus.

Führen Sie die Ziele noch einmal aus, um zu prüfen, ob die App und das Startbildschirm-Widget funktionieren.

  1. Wählen Sie das App-Schema in Xcode aus, um das App-Ziel auszuführen.
  2. Wählen Sie das Erweiterungsschema in Xcode aus, um das Erweiterungsziel auszuführen.
  3. Rufen Sie in der App eine Artikelseite auf.
  4. Klicken Sie auf die Schaltfläche, um den Anzeigentitel zu aktualisieren. Das Startbildschirm-Widget sollte auch die Überschrift aktualisieren.

Android-Code aktualisieren

Füge die XML-Datei des Startbildschirm-Widgets hinzu.

Aktualisieren Sie in Android Studio die im vorherigen Schritt generierten Dateien.Öffnen Sie die Datei res/layout/news_widget.xml. Es definiert die Struktur und das Layout Ihres Startbildschirm-Widgets. Wählen Sie oben rechts Code aus und ersetzen Sie den Inhalt dieser Datei durch den folgenden Code:

android/app/res/layout/news_widget.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/widget_container"
   style="@style/Widget.Android.AppWidget.Container"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:background="@android:color/white"
   android:theme="@style/Theme.Android.AppWidgetContainer">
   
   <TextView
       android:id="@+id/headline_title"
       style="@style/Widget.Android.AppWidget.InnerView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true"
       android:layout_marginStart="8dp"
       android:layout_marginLeft="8dp"
       android:background="@android:color/white"
       android:text="Title"
       android:textSize="20sp"
       android:textStyle="bold" />

   <TextView
       android:id="@+id/headline_description"
       style="@style/Widget.Android.AppWidget.InnerView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@+id/headline_title"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true"
       android:layout_marginStart="8dp"
       android:layout_marginLeft="8dp"
       android:layout_marginTop="4dp"
       android:background="@android:color/white"
       android:text="Title"
       android:textSize="16sp" />

</RelativeLayout>

Dieser XML-Code definiert zwei Textansichten, eine für die Artikelüberschrift und eine für die Artikelbeschreibung. In diesen Textansichten wird auch der Stil definiert. In diesem Codelab werden Sie immer wieder zu dieser Datei zurückkehren.

NewsWidget-Funktionalität aktualisieren

Öffnen Sie die Kotlin-Quellcodedatei NewsWidget.kt. Diese Datei enthält eine generierte Klasse namens NewsWidget, die die Klasse AppWidgetProvider erweitert.

Die Klasse NewsWidget enthält drei Methoden aus ihrer Basisklasse. Sie ändern die Methode onUpdate. Android ruft diese Methode für Widgets in festen Intervallen auf.

Ersetzen Sie den Inhalt der Datei NewsWidget.kt durch den folgenden Code:

android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt

// Import will depend on App ID.
package com.mydomain.homescreen_widgets

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews

// New import.
import es.antonborri.home_widget.HomeWidgetPlugin


/**
 * Implementation of App Widget functionality.
 */
class NewsWidget : AppWidgetProvider() {
    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray,
    ) {
        for (appWidgetId in appWidgetIds) {
            // Get reference to SharedPreferences
            val widgetData = HomeWidgetPlugin.getData(context)
            val views = RemoteViews(context.packageName, R.layout.news_widget).apply {

                val title = widgetData.getString("headline_title", null)
                setTextViewText(R.id.headline_title, title ?: "No title set")

                val description = widgetData.getString("headline_description", null)
                setTextViewText(R.id.headline_description, description ?: "No description set")
            }

            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Wenn jetzt onUpdate aufgerufen wird, ruft Android mithilfe der Methode the widgetData.getString() die neuesten Werte aus dem lokalen Speicher ab und ruft dann setTextViewText auf, um den auf dem Startbildschirm angezeigten Text zu ändern.

Updates testen

Teste die App, um zu prüfen, ob deine Startbildschirm-Widgets mit neuen Daten aktualisiert werden. Du kannst die Daten auf den Artikelseiten über die Schaltfläche Startbildschirm aktualisieren FloatingActionButton aktualisieren. Ihr Startbildschirm-Widget sollte mit dem Titel des Artikels aktualisiert werden.

5ce1c9914b43ad79.png

5. Benutzerdefinierte Schriftarten der Flutter App im iOS-Startbildschirm-Widget verwenden

Bisher haben Sie das Startbildschirm-Widget so konfiguriert, dass die von der Flutter App bereitgestellten Daten gelesen werden. Die Flutter App enthält eine benutzerdefinierte Schriftart, die Sie für das Startbildschirm-Widget verwenden können. Sie können die benutzerdefinierte Schriftart im Widget für den iOS-Startbildschirm verwenden. Die Verwendung benutzerdefinierter Schriftarten in Startbildschirm-Widgets ist auf Android-Geräten nicht verfügbar.

iOS-Code aktualisieren

Flutter speichert seine Assets im mainBundle der iOS-Apps. Über den Code des Startbildschirm-Widgets können Sie auf die Assets in diesem Set zugreifen.

Nehmen Sie in der Struktur NewsWidgetsEntryView in Ihrer Datei NewsWidgets.swift die folgenden Änderungen vor:

Erstellen Sie eine Hilfsfunktion, um den Pfad zum Flutter-Asset-Verzeichnis zu erhalten:

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

Registrieren Sie die Schriftart mithilfe der URL Ihrer benutzerdefinierten Schriftartdatei.

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

Die benutzerdefinierte Schriftart in der Textansicht für Anzeigentitel verwenden

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

Wenn Sie das Startbildschirm-Widget ausführen, wird jetzt die benutzerdefinierte Schriftart für die Überschrift verwendet, wie in der folgenden Abbildung dargestellt:

93f8b9d767aacfb2.png

6. Flutter-Widgets als Bild rendern

In diesem Abschnitt zeigen Sie eine Grafik aus der Flutter-App als Startbildschirm-Widget an.

Dieses Widget bietet eine größere Herausforderung als der Text, den Sie auf dem Startbildschirm angezeigt haben. Es ist viel einfacher, das Flutter-Diagramm als Bild darzustellen, als es mit nativen UI-Komponenten neu zu erstellen.

Codieren Sie Ihr Startbildschirm-Widget so, dass Ihr Flutter-Diagramm als PNG-Datei gerendert wird. Dieses Bild kann im Startbildschirm-Widget angezeigt werden.

Dart-Code schreiben

Fügen Sie auf Dart-Seite die Methode renderFlutterWidget aus dem Paket „home_widget“ hinzu. Diese Methode verwendet ein Widget, einen Dateinamen und einen Schlüssel. Sie gibt ein Bild des Flutter-Widgets zurück und speichert es in einem gemeinsamen Container. Geben Sie den Bildnamen im Code an und prüfen Sie, ob das Startbildschirm-Widget auf den Container zugreifen kann. Die key speichert den vollständigen Dateipfad als String im lokalen Speicher des Geräts. So kann das Startbildschirm-Widget die Datei finden, wenn sich der Name im Dart-Code ändert.

In diesem Codelab stellt die Klasse LineChart in der Datei lib/article_screen.dart das Diagramm dar. Die zugehörige Build-Methode gibt einen CustomPainter zurück, mit dem dieses Diagramm auf den Bildschirm übertragen wird.

Öffnen Sie die Datei lib/article_screen.dart, um dieses Feature zu implementieren. Importieren Sie das Paket „home_widget“. Ersetzen Sie als Nächstes den Code in der Klasse _ArticleScreenState durch den folgenden Code:

lib/article_screen.dart

import 'package:flutter/material.dart';
// New: import the home_widget package.
import 'package:home_widget/home_widget.dart';

import 'home_screen.dart';
import 'news_data.dart';

...

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.article.title!),
      ),
      // New: add this FloatingActionButton
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () async {
          if (_globalKey.currentContext != null) {
            var path = await HomeWidget.renderFlutterWidget(
              const LineChart(),
              fileName: 'screenshot',
              key: 'filename',
              logicalSize: _globalKey.currentContext!.size,
              pixelRatio:
                  MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
            );
            setState(() {
              imagePath = path as String?;
            });
          }
          updateHeadline(widget.article);
        },
        label: const Text('Update Homescreen'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16.0),
        children: [
          Text(
            widget.article.description!,
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 20.0),
          Text(widget.article.articleText!),
          const SizedBox(height: 20.0),
          Center(
            // New: Add this key
            key: _globalKey,
            child: const LineChart(),
          ),
          const SizedBox(height: 20.0),
          Text(widget.article.articleText!),
        ],
      ),
    );
  }
}

In diesem Beispiel werden drei Änderungen an der Klasse _ArticleScreenState vorgenommen.

Erstellt einen GlobalKey

GlobalKey ruft den Kontext des jeweiligen Widgets ab, der zum Abrufen der Größe dieses Widgets erforderlich ist .

lib/article_screen.dart

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

Fügt „imagePath“ hinzu

Mit der Eigenschaft imagePath wird die Position des Bilds gespeichert, an der das Flutter-Widget gerendert wird.

lib/article_screen.dart

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

Fügt den Schlüssel dem zu rendernden Widget hinzu

_globalKey enthält das Flutter-Widget, das für das Bild gerendert wird. In diesem Fall ist das Flutter-Widget das Zentrum, das LineChart enthält.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. Speichert das Widget als Bild

Die Methode renderFlutterWidget wird aufgerufen, wenn der Nutzer auf floatingActionButton klickt. Die Methode speichert die resultierende PNG-Datei als "Screenshot". in das Verzeichnis des freigegebenen Containers. Die Methode speichert auch den vollständigen Pfad zum Image als Dateinamenschlüssel im Gerätespeicher.

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

iOS-Code aktualisieren

Bei iOS aktualisieren Sie den Code, um den Dateipfad aus dem Speicher abzurufen und die Datei mithilfe von SwiftUI als Bild anzuzeigen.

Öffnen Sie die Datei NewsWidgets.swift und nehmen Sie folgende Änderungen vor:

Füge filename und displaySize zur Struktur NewsArticleEntry hinzu.

Das Attribut filename enthält den String, der den Pfad zur Bilddatei darstellt. Das Attribut displaySize enthält die Größe des Startbildschirm-Widgets auf dem Gerät des Nutzers. Die Größe des Startbildschirm-Widgets stammt von context.

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

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

Die Funktion placeholder aktualisieren

Fügen Sie die Platzhalter filename und displaySize ein.

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

Dateinamen aus userDefaults in getSnapshot abrufen

Dadurch wird die Variable filename auf den Wert filename im userDefaults-Speicher gesetzt, wenn das Startbildschirm-Widget aktualisiert wird.

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

ChartImage erstellen, das das Bild aus einem Pfad anzeigt

In der Ansicht ChartImage wird ein Bild aus den Inhalten der auf Dart-Seite generierten Datei erstellt. Hier legen Sie die Größe auf 50% des Frames fest.

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

ChartImage im Text von NewsWidgetsEntryView verwenden

Fügen Sie die ChartImage-Ansicht zum Hauptteil von NewsWidgetsEntryView hinzu, um das ChartImage im Startbildschirm-Widget anzuzeigen.

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
}

Änderungen testen

Um die Änderungen zu testen, führen Sie sowohl das Ziel der Flutter-App (Ausführer) als auch das Erweiterungsziel aus Xcode noch einmal aus. Wenn Sie sich das Bild ansehen möchten, rufen Sie eine der Artikelseiten in der App auf und drücken Sie die Taste, um das Startbildschirm-Widget zu aktualisieren.

33bdfe2cce908c48.png

Android-Code aktualisieren

Der Android-Code funktioniert genauso wie der iOS-Code.

  1. Öffnen Sie die Datei android/app/res/layout/news_widget.xml. Es enthält die UI-Elemente Ihres Startbildschirm-Widgets. Ersetzen Sie den Inhalt durch den folgenden Code:

android/app/res/layout/news_widget.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/widget_container"
   style="@style/Widget.Android.AppWidget.Container"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:background="@android:color/white"
   android:theme="@style/Theme.Android.AppWidgetContainer">

   <TextView
       android:id="@+id/headline_title"
       style="@style/Widget.Android.AppWidget.InnerView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true"
       android:layout_marginStart="8dp"
       android:layout_marginLeft="8dp"
       android:background="@android:color/white"
       android:text="Title"
       android:textSize="20sp"
       android:textStyle="bold" />

   <TextView
       android:id="@+id/headline_description"
       style="@style/Widget.Android.AppWidget.InnerView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@+id/headline_title"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true"
       android:layout_marginStart="8dp"
       android:layout_marginLeft="8dp"
       android:layout_marginTop="4dp"
       android:background="@android:color/white"
       android:text="Title"
       android:textSize="16sp" />
   
   <!--New: add this image view -->
   <ImageView
       android:id="@+id/widget_image"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:layout_below="@+id/headline_description"
       android:layout_alignBottom="@+id/headline_title"
       android:layout_alignParentStart="true"
       android:layout_alignParentLeft="true"
       android:layout_marginStart="8dp"
       android:layout_marginLeft="8dp"
       android:layout_marginTop="6dp"
       android:layout_marginBottom="-134dp"
       android:layout_weight="1"
       android:adjustViewBounds="true"
       android:background="@android:color/white"
       android:scaleType="fitCenter"
       android:src="@android:drawable/star_big_on"
       android:visibility="visible"
       tools:visibility="visible" />

</RelativeLayout>

Mit diesem neuen Code wird dem Startbildschirm-Widget ein Bild hinzugefügt, das vorerst ein allgemeines Sternsymbol anzeigt. Ersetzen Sie dieses Sternsymbol durch das Bild, das Sie im Dart-Code gespeichert haben.

  1. Öffnen Sie die Datei NewsWidget.kt. Ersetzen Sie den Inhalt durch den folgenden Code:

android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt

// Import will depend on App ID.
package com.mydomain.homescreen_widgets

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import java.io.File
import es.antonborri.home_widget.HomeWidgetPlugin


/**
 * Implementation of App Widget functionality.
 */
class NewsWidget : AppWidgetProvider() {
    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray,
    ) {
        for (appWidgetId in appWidgetIds) {
            val widgetData = HomeWidgetPlugin.getData(context)
            val views = RemoteViews(context.packageName, R.layout.news_widget).apply {

                val title = widgetData.getString("headline_title", null)
                setTextViewText(R.id.headline_title, title ?: "No title set")

                val description = widgetData.getString("headline_description", null)
                setTextViewText(R.id.headline_description, description ?: "No description set")

                // New: Add the section below
               // Get chart image and put it in the widget, if it exists
                val imageName = widgetData.getString("filename", null)
                val imageFile = File(imageName)
                val imageExists = imageFile.exists()
                if (imageExists) {
                    val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
                    setImageViewBitmap(R.id.widget_image, myBitmap)
                } else {
                    println("image not found!, looked @: ${imageName}")
                }
                // End new code
            }

            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
}

Dieser Dart-Code speichert einen Screenshot mit dem filename-Schlüssel im lokalen Speicher. Außerdem ruft sie den vollständigen Pfad des Bildes ab und erstellt daraus ein File-Objekt. Wenn das Bild vorhanden ist, ersetzt der Dart-Code das Bild im Startbildschirm-Widget durch das neue Bild.

  1. Aktualisiere deine App und rufe einen Artikelbildschirm auf. Tippen Sie auf Startbildschirm aktualisieren. Das Startbildschirm-Widget zeigt das Diagramm an.

7. Nächste Schritte

Glückwunsch!

Herzlichen Glückwunsch! Sie haben erfolgreich Startbildschirm-Widgets für Ihre Flutter-Apps für iOS und Android erstellt.

Links zu Inhalten in Ihrer Flutter-App

Möglicherweise möchten Sie den Nutzer auf eine bestimmte Seite in Ihrer App weiterleiten, je nachdem, auf welche Seite er klickt. In der Nachrichten-App aus diesem Codelab möchten Sie beispielsweise, dass der Nutzer den Nachrichtenartikel für die angezeigte Schlagzeile sieht.

Diese Funktion wird in diesem Codelab nicht behandelt. Sie finden Beispiele für die Verwendung eines Streams, der vom Paket „home_widget“ bereitgestellt wird, um App-Starts von Startbildschirm-Widgets zu identifizieren und Nachrichten über das Startbildschirm-Widget über die URL zu senden. Weitere Informationen finden Sie in der Dokumentation zu Deeplinks unter docs.flutter.dev.

Widget im Hintergrund aktualisieren

In diesem Codelab haben Sie über eine Schaltfläche eine Aktualisierung des Startbildschirm-Widgets ausgelöst. Obwohl dies für Tests sinnvoll ist, sollte im Produktionscode Ihre App das Startbildschirm-Widget im Hintergrund aktualisieren. Mit dem Workmanager-Plug-in können Sie Hintergrundaufgaben erstellen, um Ressourcen zu aktualisieren, die das Startbildschirm-Widget benötigt. Weitere Informationen finden Sie im Abschnitt Hintergrundaktualisierung des Pakets „home_widget“.

Bei iOS kann das Startbildschirm-Widget auch eine Netzwerkanfrage zum Aktualisieren der Benutzeroberfläche senden. Über die Zeitachse können Sie die Bedingungen oder die Häufigkeit dieser Anfrage steuern. Weitere Informationen zur Verwendung der Zeitachse finden Sie im Apple-Widget „Widgets auf dem neuesten Stand halten“. Dokumentation.

Weitere Informationen