Ajouter un widget d'écran d'accueil à votre application Flutter

1. Introduction

Que sont les widgets ?

Pour les développeurs Flutter, la définition commune de widget fait référence aux composants d'interface utilisateur créés à l'aide du framework Flutter. Dans le cadre de cet atelier de programmation, un widget fait référence à une version miniature d'une application qui permet d'afficher les informations de l'application sans ouvrir celle-ci. Sur Android, les widgets s'affichent sur l'écran d'accueil. Sur iOS, vous pouvez les ajouter à l'écran d'accueil, à l'écran de verrouillage ou à la vue "Aujourd'hui".

f0027e8a7d0237e0.png b991e79ea72c8b65.png

Quelle est la complexité d'un widget ?

La plupart des widgets de l'écran d'accueil sont simples. Elles peuvent contenir du texte de base, des images simples ou, sur Android, des commandes de base. Android et iOS limitent les composants d'interface utilisateur et les fonctionnalités que vous pouvez utiliser.

819b9fffd700e571.png 92d62ccfd17d770d.png

Créer l'interface utilisateur des widgets

En raison de ces limites d'interface utilisateur, vous ne pouvez pas dessiner directement l'interface utilisateur d'un widget de l'écran d'accueil à l'aide du framework Flutter. À la place, vous pouvez ajouter à votre application Flutter des widgets créés avec des frameworks de plate-forme tels que Jetpack Compose ou SwiftUI. Cet atelier de programmation présente des exemples de partage de ressources entre votre application et les widgets pour éviter de réécrire une UI complexe.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez créer des widgets d'écran d'accueil sur Android et iOS pour une application Flutter simple à l'aide du package home_widget, qui permet aux utilisateurs de lire des articles. Vos widgets:

  • Affichez les données de votre application Flutter.
  • Affichez du texte à l'aide des éléments de police partagés depuis l'application Flutter.
  • Affichez l'image d'un widget Flutter rendu.

a36b7ba379151101.png

Cette application Flutter comprend deux écrans (ou routes):

  • La première affiche une liste d'articles de presse avec des titres et des descriptions.
  • La seconde affiche l'article complet avec un graphique créé à l'aide de CustomPaint.

.

9c02f8b62c1faa3a.png d97d44051304cae4.png

Points abordés

  • Créer des widgets sur l'écran d'accueil sur iOS et Android
  • Utiliser le package home_widget pour partager des données entre le widget de l'écran d'accueil et votre application Flutter
  • Découvrez comment réduire la quantité de code à réécrire.
  • Mettre à jour le widget de l'écran d'accueil depuis votre application Flutter

2. Configurer l'environnement de développement

Pour les deux plates-formes, vous avez besoin du SDK Flutter et d'un IDE. Vous pouvez utiliser l'IDE de votre choix pour travailler avec Flutter. Il peut s'agir de Visual Studio Code avec les extensions Dart Code et Flutter, ou d'Android Studio ou d'IntelliJ avec les plug-ins Flutter et Dart installés.

Pour créer le widget de l'écran d'accueil iOS:

  • Vous pouvez exécuter cet atelier de programmation sur un appareil iOS physique ou sur le simulateur iOS.
  • Vous devez configurer un système macOS avec l'IDE Xcode. Le compilateur requis pour créer la version iOS de votre application est alors installé.

Pour créer le widget de l'écran d'accueil Android:

  • Vous pouvez exécuter cet atelier de programmation sur un appareil Android physique ou sur l'émulateur Android.
  • Vous devez configurer votre système de développement avec Android Studio. Cette action permet d'installer le compilateur nécessaire à la compilation de la version Android de votre application.

Télécharger le code de démarrage

Télécharger la version initiale du projet sur GitHub

À partir de la ligne de commande, clonez le dépôt GitHub dans un répertoire flutter-codelabs:

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

Après avoir cloné le dépôt, vous trouverez le code de cet atelier de programmation dans le répertoire flutter-codelabs/homescreen_codelab. Ce répertoire contient le code du projet finalisé pour chaque étape de l'atelier de programmation.

Ouvrir l'application de démarrage

Ouvrez le répertoire flutter-codelabs/homescreen_codelab/step_03 dans l'IDE de votre choix.

Installer les packages

Tous les packages requis ont été ajoutés au fichier pubspec.yaml du projet. Pour récupérer les dépendances du projet, exécutez la commande suivante:

$ flutter pub get

3. Ajouter un widget de base sur l'écran d'accueil

Tout d'abord, ajoutez le widget de l'écran d'accueil à l'aide des outils de la plate-forme native.

Créer un widget de base pour l'écran d'accueil iOS

L'ajout d'une extension d'application à votre application iOS Flutter est semblable à l'ajout d'une extension d'application à une application SwiftUI ou UIKit:

  1. Exécutez open ios/Runner.xcworkspace dans une fenêtre de terminal à partir du répertoire de votre projet Flutter. Vous pouvez également effectuer un clic droit sur le dossier ios à partir de VSCode, puis sélectionner Ouvrir dans Xcode. L'espace de travail Xcode par défaut s'ouvre dans votre projet Flutter.
  2. Dans le menu, sélectionnez File → New → Target (Fichier → Nouveau → Cible). Une nouvelle cible est alors ajoutée au projet.
  3. La liste des modèles s'affiche. Sélectionnez Extension de widget.
  4. Saisissez "NewsWidgets". dans le champ Nom du produit pour ce widget. Décochez les cases Inclure l'activité en direct et Inclure l'intent de configuration.

Inspecter l'exemple de code

Lorsque vous ajoutez une cible, Xcode génère un exemple de code basé sur le modèle que vous avez sélectionné. Pour en savoir plus sur le code généré et sur WidgetKit, consultez la documentation sur les extensions d'application d'Apple.

Déboguer et tester votre exemple de widget

  1. Commencez par mettre à jour la configuration de votre application Flutter. Vous devez le faire lorsque vous ajoutez des packages dans votre application Flutter et que vous prévoyez d'exécuter une cible dans le projet à partir de Xcode. Pour mettre à jour la configuration de votre application, exécutez la commande suivante dans le répertoire de votre application Flutter:
$ flutter build ios --config-only
  1. Cliquez sur Runner (Exécuter) pour afficher la liste des cibles. Sélectionnez la cible du widget que vous venez de créer (NewsWidgets), puis cliquez sur Run (Exécuter). Exécutez la cible du widget à partir de Xcode lorsque vous modifiez le code du widget iOS.

bbb519df1782881d.png

  1. L'écran du simulateur ou de l'appareil doit afficher un widget de base sur l'écran d'accueil. Si vous ne la voyez pas, vous pouvez l'ajouter à l'écran. Cliquez de manière prolongée sur l'écran d'accueil, puis cliquez sur + en haut à gauche.

18eff1cae152014d.png

  1. Recherchez le nom de l'application. Pour cet atelier de programmation, recherchez "Widgets pour l'écran d'accueil".

a0c00df87615493e.png

  1. Une fois que vous avez ajouté le widget de l'écran d'accueil, il doit afficher un simple texte indiquant l'heure.

Créer un widget Android de base

  1. Pour ajouter un widget de l'écran d'accueil dans Android, ouvrez le fichier de compilation du projet dans Android Studio. Vous pouvez trouver ce fichier dans android/build.gradle. Vous pouvez également effectuer un clic droit sur le dossier android dans VSCode, puis sélectionner Ouvrir dans Android Studio.
  2. Une fois le projet créé, localisez le répertoire de l'application en haut à gauche. Ajoutez votre nouveau widget d'écran d'accueil à ce répertoire. Effectuez un clic droit sur le répertoire, puis sélectionnez New -> (Nouveau ->) Widget -> Widget d'application.

f19d8b7f95ab884e.png

  1. Android Studio affiche un nouveau formulaire. Ajoutez des informations de base sur votre widget de l'écran d'accueil, y compris le nom de la classe, son emplacement, sa taille et la langue source.

Pour cet atelier de programmation, définissez les valeurs suivantes:

  • Class Name (Nom de classe) dans NewsWidget
  • Menu déroulant Largeur minimale (cellules) sur 3
  • Menu déroulant Hauteur minimale (cellules) sur 3

Inspecter l'exemple de code

Lorsque vous envoyez le formulaire, Android Studio crée et met à jour plusieurs fichiers. Les modifications pertinentes pour cet atelier de programmation sont répertoriées dans le tableau ci-dessous

Action

Fichier cible

Modifier

Mettre à jour

AndroidManifest.xml

Ajoute un nouveau récepteur qui enregistre le widget NewsWidget.

Créer

res/layout/news_widget.xml

Définit l'interface utilisateur du widget de l'écran d'accueil.

Créer

res/xml/news_widget_info.xml

Définit la configuration du widget de l'écran d'accueil. Vous pouvez ajuster les dimensions ou le nom de votre widget dans ce fichier.

Créer

java/com/example/homescreen_widgets/NewsWidget.kt

Contient votre code Kotlin pour ajouter des fonctionnalités à votre widget de l'écran d'accueil.

Vous trouverez plus de détails sur ces fichiers tout au long de cet atelier de programmation.

Déboguer et tester votre exemple de widget

Exécutez maintenant votre application et affichez le widget de l'écran d'accueil. Une fois l'application créée, accédez à l'écran de sélection de l'application sur votre appareil Android et appuyez de manière prolongée sur l'icône de ce projet Flutter. Sélectionnez Widgets dans le menu pop-up.

dff7c9f9f85ef1c7.png

L'appareil ou l'émulateur Android affiche le widget de l'écran d'accueil par défaut pour Android.

4. Envoyer des données de votre application Flutter vers le widget de l'écran d'accueil

Vous pouvez personnaliser le widget de base de l'écran d'accueil que vous avez créé. Mettez à jour le widget de l'écran d'accueil pour afficher un titre et un résumé pour un article d'actualité. La capture d'écran suivante montre un exemple de widget de l'écran d'accueil affichant un titre et un résumé.

acb90343a3e51b6d.png

Pour transmettre des données entre votre application et le widget de l'écran d'accueil, vous devez écrire du code natif et Dart. Ce processus est divisé en trois parties:

  1. Écrire dans votre application Flutter du code Dart compatible avec Android et iOS
  2. Ajouter des fonctionnalités iOS natives
  3. Ajouter des fonctionnalités Android natives

Utiliser des groupes d'applications iOS

Pour partager des données entre une application iOS parente et une extension de widget, les deux cibles doivent appartenir au même groupe d'applications. Pour en savoir plus sur les groupes d'applications, consultez la documentation sur les groupes d'applications d'Apple.

Mettez à jour votre identifiant de bundle:

Dans Xcode, accédez aux paramètres de votre cible. Dans la section Signing & Capacités, vérifiez que l'identifiant de votre équipe et de votre groupe est défini.

Ajoutez le groupe d'applications à la fois à la cible Runner et à la cible NewsWidgetExtension dans Xcode:

Sélectionnez + Capability -> (+ Capacité ->). Groupes d'applications et ajoutez un nouveau groupe d'applications. Répétez l'opération pour la cible Runner (application parent) et la cible du widget.

135e1a8c4652dac.png

Ajouter le code Dart

Les applications iOS et Android peuvent partager des données avec une application Flutter de différentes manières.Pour communiquer avec ces applications, utilisez le key/value local de l'appareil. Pour iOS, ce magasin est appelé UserDefaults, et Android l'appelle SharedPreferences. Le package home_widget encapsule ces API pour simplifier l'enregistrement des données sur les deux plates-formes et permet aux widgets de l'écran d'accueil d'extraire les données mises à jour.

707ae86f6650ac55.png

Les données relatives au titre et à la description proviennent du fichier news_data.dart. Ce fichier contient des données fictives et une classe de données 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,
  });
}

Modifier les valeurs du titre et de la description

Pour ajouter la fonctionnalité permettant de mettre à jour le widget de l'écran d'accueil à partir de votre application Flutter, accédez au fichier lib/home_screen.dart. Remplacez le contenu du fichier par le code suivant. Remplacez ensuite <YOUR APP GROUP> par l'identifiant de votre groupe d'applications.

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 fonction updateHeadline enregistre les paires clé/valeur dans l'espace de stockage local de votre appareil. La clé headline_title contient la valeur de newHeadline.title. La clé headline_description contient la valeur de newHeadline.description. Elle informe également la plate-forme native que les nouvelles données des widgets de l'écran d'accueil peuvent être récupérées et affichées.

Modifier lefloatActionButton

Appelez la fonction updateHeadline lorsque vous appuyez sur floatingActionButton, comme indiqué ci-dessous:

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

Avec cette modification, lorsqu'un utilisateur appuie sur le bouton Mettre à jour le titre sur la page d'un article, les détails du widget de l'écran d'accueil sont mis à jour.

Modifier le code iOS pour afficher les données de l'article

Pour mettre à jour le widget de l'écran d'accueil pour iOS, utilisez Xcode.

Ouvrez le fichier NewsWidgets.swift dans Xcode:

Configurez le TimelineEntry.

Remplacez la structure SimpleEntry par le code suivant:

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
}

Cette structure NewsArticleEntry définit les données entrantes à transmettre au widget de l'écran d'accueil lors de sa mise à jour. Le type TimelineEntry nécessite un paramètre de date.Pour en savoir plus sur le protocole TimelineEntry, consultez la documentation Apple TimelineEntry.

Modifiez l'élément View qui affiche le contenu.

Modifiez le widget de l'écran d'accueil pour qu'il affiche le titre et la description de l'article de presse plutôt que la date. Pour afficher du texte en SwiftUI, utilisez la vue Text. Pour empiler des vues dans SwiftUI, utilisez la vue VStack.

Remplacez la vue NewsWidgetEntryView générée par le code suivant:

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

Modifier le fournisseur pour indiquer au widget de l'écran d'accueil quand et comment effectuer la mise à jour

Remplacez le Provider existant par le code suivant. Remplacez ensuite <YOUR APP GROUP> par l'identifiant de votre groupe d'applications :

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

Le Provider dans le code précédent est conforme à un TimelineProvider. Provider propose trois méthodes différentes:

  1. La méthode placeholder génère une entrée d'espace réservé lorsque l'utilisateur affiche pour la première fois un aperçu du widget de l'écran d'accueil.

45a0f64240c12efe.png

  1. La méthode getSnapshot lit les données à partir des valeurs par défaut de l'utilisateur et génère l'entrée pour l'heure actuelle.
  2. La méthode getTimeline renvoie des entrées de chronologie. Cela s'avère utile lorsque vous pouvez mettre à jour votre contenu à des moments prévisibles. Cet atelier de programmation utilise la fonction getSnapshot pour obtenir l'état actuel. La méthode .atEnd indique au widget de l'écran d'accueil d'actualiser les données une fois l'heure actuelle écoulée.

Mettez en commentaire le NewsWidgets_Previews

L'utilisation d'aperçus n'est pas abordée dans cet atelier de programmation. Pour en savoir plus sur la prévisualisation des widgets de l'écran d'accueil SwiftUI, consultez la documentation d'Apple sur le débogage des widgets.

Enregistrez tous les fichiers, puis réexécutez l'application et la cible du widget.

Exécutez à nouveau les cibles pour vérifier que l'application et le widget de l'écran d'accueil fonctionnent.

  1. Sélectionnez le schéma d'application dans Xcode pour exécuter la cible de l'application.
  2. Sélectionnez le schéma de l'extension dans Xcode pour exécuter la cible de l'extension.
  3. Accédez à la page d'un article dans l'application.
  4. Cliquez sur le bouton pour modifier le titre. Le widget de l'écran d'accueil doit également modifier le titre.

Mettre à jour le code Android

Ajoutez le fichier XML du widget de l'écran d'accueil.

Dans Android Studio, mettez à jour les fichiers générés à l'étape précédente.Ouvrez le fichier res/layout/news_widget.xml. Il définit la structure et la mise en page de votre widget sur l'écran d'accueil. Sélectionnez Code en haut à droite et remplacez le contenu de ce fichier par le code suivant:

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>

Ce fichier XML définit deux affichages de texte, l'un pour le titre de l'article et l'autre pour la description de l'article. Ces affichages de texte définissent également le style. Vous reviendrez sur ce fichier tout au long de cet atelier de programmation.

Mettre à jour la fonctionnalité NewsWidget

Ouvrez le fichier de code source Kotlin NewsWidget.kt. Ce fichier contient une classe générée appelée NewsWidget qui étend la classe AppWidgetProvider.

La classe NewsWidget contient trois méthodes de sa super-classe. Vous allez modifier la méthode onUpdate. Android appelle cette méthode pour les widgets à intervalles fixes.

Remplacez le contenu du fichier NewsWidget.kt par le code suivant:

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

Désormais, lorsque onUpdate est appelé, Android récupère les valeurs les plus récentes du stockage local à l'aide de la méthode the widgetData.getString(), puis appelle setTextViewText pour modifier le texte affiché dans le widget de l'écran d'accueil.

Tester les mises à jour

Testez l'application pour vous assurer que les widgets de l'écran d'accueil se mettent à jour avec les nouvelles données. Pour modifier les données, utilisez l'option Mettre à jour l'écran d'accueil FloatingActionButton sur les pages des articles. Le widget de l'écran d'accueil devrait se mettre à jour avec le titre de l'article.

5ce1c9914b43ad79.png

5. Utiliser les polices personnalisées de l'application Flutter dans le widget de l'écran d'accueil iOS

Jusqu'à présent, vous avez configuré le widget de l'écran d'accueil pour qu'il lise les données fournies par l'application Flutter. L'application Flutter inclut une police personnalisée que vous pouvez utiliser dans le widget de l'écran d'accueil. Vous pouvez utiliser la police personnalisée dans le widget de l'écran d'accueil iOS. L'utilisation de polices personnalisées dans les widgets de l'écran d'accueil n'est pas disponible sur Android.

Mettre à jour le code iOS

Flutter stocke ses éléments dans le mainBundle d'applications iOS. Vous pouvez accéder aux éléments de ce pack à partir du code du widget de l'écran d'accueil.

Dans la structure NewsWidgetsEntryView de votre fichier NewsWidgets.swift, apportez les modifications suivantes :

Créez une fonction d'assistance pour obtenir le chemin d'accès au répertoire des éléments 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
       }
   ...
}

Enregistrez la police à l'aide de l'URL de votre fichier de police personnalisée.

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

Mettez à jour l'affichage Texte du titre pour utiliser votre police personnalisée.

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

Lorsque vous exécutez le widget de l'écran d'accueil, il utilise désormais la police personnalisée pour le titre, comme illustré dans l'image suivante:

93f8b9d767aacfb2.png

6. Afficher des widgets Flutter en tant qu'image

Dans cette section, vous allez afficher un graphique de votre application Flutter en tant que widget de l'écran d'accueil.

Ce widget est plus complexe que le texte affiché sur l'écran d'accueil. Il est beaucoup plus facile d'afficher le graphique Flutter sous forme d'image que d'essayer de le recréer à l'aide de composants d'UI natifs.

Codez votre widget de l'écran d'accueil pour afficher votre graphique Flutter au format PNG. Le widget de l'écran d'accueil peut afficher cette image.

Écrire le code Dart

Du côté de Dart, ajoutez la méthode renderFlutterWidget du package home_widget. Cette méthode utilise un widget, un nom de fichier et une clé. Il renvoie une image du widget Flutter et l'enregistre dans un conteneur partagé. Indiquez le nom de l'image dans votre code et assurez-vous que le widget de l'écran d'accueil peut accéder au conteneur. key enregistre le chemin d'accès complet au fichier sous forme de chaîne dans l'espace de stockage local de l'appareil. Cela permet au widget de l'écran d'accueil de trouver le fichier si son nom change dans le code Dart.

Pour cet atelier de programmation, la classe LineChart du fichier lib/article_screen.dart représente le graphique. Sa méthode de compilation renvoie un CustomPainter qui affiche ce graphique à l'écran.

Pour implémenter cette fonctionnalité, ouvrez le fichier lib/article_screen.dart. Importez le package home_widget. Remplacez ensuite le code de la classe _ArticleScreenState par le code suivant:

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

Cet exemple apporte trois modifications à la classe _ArticleScreenState.

Crée une GlobalKey

GlobalKey récupère le contexte du widget spécifique, qui est nécessaire pour obtenir la taille de ce widget .

lib/article_screen.dart

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

Ajoute imagePath

La propriété imagePath stocke l'emplacement de l'image où le widget Flutter est affiché.

lib/article_screen.dart

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

Ajoute la clé au widget pour le rendu

_globalKey contient le widget Flutter qui s'affiche dans l'image. Dans ce cas, le widget Flutter est le centre qui contient LineChart.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. Enregistre le widget en tant qu'image

La méthode renderFlutterWidget est appelée lorsque l'utilisateur clique sur floatingActionButton. La méthode enregistre le fichier PNG obtenu en tant que "capture d'écran". vers le répertoire partagé du conteneur. Elle enregistre également le chemin d'accès complet à l'image en tant que clé de nom de fichier dans l'espace de stockage de l'appareil.

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

Modifier le code iOS

Pour iOS, mettez à jour le code pour récupérer le chemin d'accès au fichier à partir de l'espace de stockage et afficher le fichier en tant qu'image à l'aide de SwiftUI.

Ouvrez le fichier NewsWidgets.swift pour apporter les modifications suivantes:

Ajoutez filename et displaySize au struct NewsArticleEntry.

La propriété filename contient la chaîne représentant le chemin d'accès au fichier image. La propriété displaySize contient la taille du widget de l'écran d'accueil sur l'appareil de l'utilisateur. La taille du widget de l'écran d'accueil est déterminée par context.

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

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

Mettez à jour la fonction placeholder.

Incluez un espace réservé filename et 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)
    }

Récupérez le nom de fichier à partir de userDefaults dans getSnapshot.

Cela définit la variable filename sur la valeur filename dans l'espace de stockage userDefaults lors de la mise à jour du widget de l'écran d'accueil.

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

Créer une image ChartImage qui affiche l'image à partir d'un chemin d'accès

La vue ChartImage crée une image à partir du contenu du fichier généré côté Dart. Ici, vous définissez la taille sur 50% du cadre.

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

Utilisez l'élément ChartImage dans le corps de NewsWidgetsEntryView.

Ajoutez la vue ChartImage au corps de NewsWidgetsEntryView pour afficher la vue ChartImage dans le widget de l'écran d'accueil.

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
}

Tester les modifications

Pour tester les modifications, exécutez de nouveau votre cible d'application Flutter (Runner) et votre cible d'extension à partir de Xcode. Pour voir l'image, accédez à l'une des pages d'article dans l'application, puis appuyez sur le bouton pour mettre à jour le widget de l'écran d'accueil.

33bdfe2cce908c48.png

Mettre à jour le code Android

Le code Android fonctionne de la même manière que le code iOS.

  1. Ouvrez le fichier android/app/res/layout/news_widget.xml. Il contient les éléments d'interface utilisateur de votre widget sur l'écran d'accueil. Remplacez son contenu par le code suivant:

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>

Ce nouveau code ajoute une image au widget de l'écran d'accueil, qui affiche (pour le moment) une icône générique en forme d'étoile. Remplacez cette icône en forme d'étoile par l'image que vous avez enregistrée dans le code Dart.

  1. Ouvrez le fichier NewsWidget.kt. Remplacez son contenu par le code suivant:

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

Ce code Dart enregistre une capture d'écran dans un espace de stockage local à l'aide de la clé filename. Elle récupère également le chemin d'accès complet de l'image et crée un objet File à partir de celle-ci. Si l'image existe déjà, le code Dart remplace l'image présente dans le widget de l'écran d'accueil par la nouvelle image.

  1. Actualisez votre application et accédez à l'écran d'un article. Appuyez sur Mettre à jour l'écran d'accueil. Le widget de l'écran d'accueil affiche le graphique.

7. Étapes suivantes

Félicitations !

Félicitations, vous avez réussi à créer des widgets d'écran d'accueil pour vos applications Flutter iOS et Android.

Créer des liens vers du contenu de votre application Flutter

Vous pouvez rediriger l'utilisateur vers une page spécifique de votre application, en fonction de l'endroit où il clique. Par exemple, dans l'application d'actualités de cet atelier de programmation, vous souhaiterez peut-être que l'utilisateur voie l'article d'actualité correspondant au titre affiché.

Cette fonctionnalité n'entre pas dans le cadre de cet atelier de programmation. Vous trouverez des exemples d'utilisation d'un flux fourni par le package home_widget pour identifier les lancements d'applications à partir des widgets de l'écran d'accueil et envoyer des messages à partir du widget de l'écran d'accueil via l'URL. Pour en savoir plus, consultez la documentation sur les liens profonds sur docs.flutter.dev.

Mettre à jour votre widget en arrière-plan

Dans cet atelier de programmation, vous avez déclenché une mise à jour du widget de l'écran d'accueil à l'aide d'un bouton. Bien que cela soit raisonnable pour les tests, dans le code de production, vous pouvez souhaiter que votre application mette à jour le widget de l'écran d'accueil en arrière-plan. Vous pouvez utiliser le plug-in Workmanager pour créer des tâches en arrière-plan afin de mettre à jour les ressources dont le widget de l'écran d'accueil a besoin. Pour en savoir plus, consultez la section Mise à jour en arrière-plan du package home_widget.

Pour iOS, vous pouvez également faire en sorte que le widget de l'écran d'accueil envoie une requête réseau pour mettre à jour son interface utilisateur. Pour contrôler les conditions ou la fréquence de cette requête, utilisez la timeline. Pour en savoir plus sur l'utilisation de la chronologie, consultez le document d'Apple intitulé Keeping a widget up to date dans la documentation Google Cloud.

Documentation complémentaire