Menambahkan widget Layar Utama ke Aplikasi Flutter Anda

1. Pengantar

Apa yang dimaksud dengan Widget?

Untuk developer Flutter, definisi umum dari widget mengacu pada komponen UI yang dibuat menggunakan framework Flutter. Dalam konteks codelab ini, widget merujuk pada versi mini aplikasi yang memberikan tampilan ke informasi aplikasi tanpa membuka aplikasi. Pada Android, widget aktif di layar utama. Di iOS, ekstensi tersebut dapat ditambahkan ke layar utama, layar kunci, atau tampilan hari ini.

f0027e8a7d0237e0.png b991e79ea72c8b65.png

Seberapa rumitkah Widget?

Sebagian besar widget Layar Utama sederhana. Aset tersebut dapat berisi teks dasar, grafis sederhana, atau, di Android, kontrol dasar. Baik Android maupun iOS membatasi komponen dan fitur UI yang dapat Anda gunakan.

819b9fffd700e571.pngS 92d62ccfd17d770d.pngS

Membuat UI untuk Widget

Karena batasan UI ini, Anda tidak dapat langsung menggambar UI widget Layar Utama menggunakan framework Flutter. Sebagai gantinya, Anda dapat menambahkan widget yang dibuat dengan framework platform seperti Jetpack Compose atau SwiftUI ke aplikasi Flutter Anda. Codelab ini membahas contoh berbagi resource antara aplikasi Anda dan widget guna menghindari penulisan ulang UI yang kompleks.

Yang akan Anda bangun

Dalam codelab ini, Anda akan mem-build widget Layar Utama di Android dan iOS untuk aplikasi Flutter sederhana menggunakan paket home_widget yang memungkinkan pengguna membaca artikel. Widget Anda akan:

  • Tampilkan data dari aplikasi Flutter Anda.
  • Menampilkan teks menggunakan aset font yang dibagikan dari aplikasi Flutter.
  • Menampilkan gambar widget Flutter yang dirender.

a36b7ba379151101.png

Aplikasi Flutter ini mencakup dua layar (atau rute):

  • Yang pertama menampilkan daftar artikel berita dengan judul dan deskripsi.
  • Yang kedua menampilkan artikel lengkap dengan diagram yang dibuat menggunakan CustomPaint.

.

9c02f8b62c1faa3a.pngS d97d44051304cae4.png

Yang akan Anda pelajari

  • Cara membuat widget Layar Utama di iOS dan Android.
  • Cara menggunakan paket home_widget untuk berbagi data antara widget Layar Utama dan aplikasi Flutter Anda.
  • Cara mengurangi jumlah kode yang perlu Anda tulis ulang.
  • Cara mengupdate widget Layar Utama dari aplikasi Flutter.

2. Menyiapkan lingkungan pengembangan

Untuk kedua platform, Anda memerlukan Flutter SDK dan IDE. Anda dapat menggunakan IDE pilihan Anda untuk bekerja dengan Flutter. Visual Studio Code dapat berupa Visual Studio Code dengan ekstensi Dart Code dan Flutter, atau Android Studio atau IntelliJ dengan plugin Flutter dan Dart yang terinstal.

Untuk membuat widget Layar Utama iOS:

  • Anda dapat menjalankan codelab ini di perangkat iOS fisik atau simulator iOS.
  • Anda harus mengonfigurasi sistem macOS dengan Xcode IDE. Tindakan ini akan menginstal compiler yang diperlukan untuk membangun aplikasi versi iOS Anda.

Untuk membuat widget Layar Utama Android:

  • Anda dapat menjalankan codelab ini di perangkat Android fisik atau emulator Android.
  • Anda harus mengonfigurasi sistem pengembangan dengan Android Studio. Tindakan ini akan menginstal compiler yang diperlukan untuk membangun aplikasi versi Android.

Mendapatkan kode awal

Mendownload versi awal project Anda dari GitHub

Dari command line, clone repositori GitHub ke dalam direktori flutter-codelabs:

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

Setelah meng-clone repo, Anda dapat menemukan kode untuk codelab ini di direktori flutter-codelabs/homescreen_codelab. Direktori ini berisi kode project yang sudah selesai untuk setiap langkah di codelab.

Membuka aplikasi awal

Buka direktori flutter-codelabs/homescreen_codelab/step_03 ke IDE pilihan Anda.

Menginstal paket

Semua paket yang diperlukan telah ditambahkan ke file pubspec.yaml project. Untuk mengambil dependensi project, jalankan perintah berikut:

$ flutter pub get

3. Menambahkan widget Layar Utama dasar

Pertama, tambahkan widget Layar Utama menggunakan alat platform native.

Membuat widget Layar Utama iOS dasar

Menambahkan ekstensi aplikasi ke aplikasi iOS Flutter mirip dengan menambahkan ekstensi aplikasi ke aplikasi SwiftUI atau UIKit:

  1. Jalankan open ios/Runner.xcworkspace di jendela terminal dari direktori project Flutter Anda. Atau, klik kanan folder ios dari VSCode dan pilih Open in Xcode. Tindakan ini akan membuka ruang kerja Xcode default di project Flutter Anda.
  2. Pilih File → New → Target dari menu. Tindakan ini akan menambahkan target baru ke project.
  3. Daftar template akan muncul. Pilih Ekstensi Widget.
  4. Ketik "NewsWidgets" ke dalam kotak Nama Produk untuk widget ini. Hapus centang pada kotak Sertakan Aktivitas Live dan Sertakan Intent Konfigurasi.

Memeriksa kode contoh

Saat Anda menambahkan target baru, Xcode akan menghasilkan kode contoh berdasarkan template yang dipilih. Untuk mengetahui informasi selengkapnya tentang kode dan WidgetKit yang dihasilkan, lihat dokumentasi ekstensi aplikasi Apple.

Men-debug dan menguji widget contoh Anda

  1. Pertama, update konfigurasi aplikasi Flutter Anda. Anda harus melakukan ini saat menambahkan paket baru di aplikasi Flutter dan berencana menjalankan target dalam project dari Xcode. Untuk mengupdate konfigurasi aplikasi, jalankan perintah berikut di direktori aplikasi Flutter Anda:
$ flutter build ios --config-only
  1. Klik Runner untuk menampilkan daftar target. Pilih target widget yang baru saja dibuat, NewsWidgets, lalu klik Run. Jalankan target widget dari Xcode saat Anda mengubah kode widget iOS.

bbb519df1782881d.png

  1. Simulator atau layar perangkat akan menampilkan widget Layar Utama dasar. Jika Anda tidak melihatnya, Anda dapat menambahkannya ke layar. Klik lama layar utama lalu klik + di sudut kiri atas.

18eff1cae152014d.pngS

  1. Telusuri nama aplikasi. Untuk codelab ini, telusuri "Widget Layar Utama"

a0c00df87615493e.png

  1. Setelah ditambahkan, widget Layar Utama akan menampilkan teks sederhana yang menunjukkan waktu.

Membuat Widget Android Dasar

  1. Untuk menambahkan widget Layar Utama di Android, buka file build project di Android Studio. Anda dapat menemukan file ini di android/build.gradle. Atau, klik kanan folder android dari VSCode dan pilih Open in Android Studio.
  2. Setelah project dibuat, cari direktori aplikasi di sudut kiri atas. Tambahkan widget Layar Utama baru Anda ke direktori ini. Klik kanan direktori, pilih New -> Widget -> Widget Aplikasi.

f19d8b7f95ab884e.png

  1. Android Studio menampilkan formulir baru. Tambahkan informasi dasar tentang widget Layar Utama Anda, termasuk nama kelas, penempatan, ukuran, dan bahasa sumbernya

Untuk codelab ini, tetapkan nilai berikut:

  • Kotak Class Name ke NewsWidget
  • Dropdown Lebar Minimum (sel) ke 3
  • Dropdown Tinggi Minimum (sel) ke 3

Memeriksa kode contoh

Saat Anda mengirimkan formulir, Android Studio akan membuat dan mengupdate beberapa file. Perubahan yang relevan untuk codelab ini tercantum dalam tabel di bawah

Tindakan

File Target

Ubah

Perbarui

AndroidManifest.xml

Menambahkan penerima baru yang mendaftarkan NewsWidget.

Buat

res/layout/news_widget.xml

Menentukan UI widget Layar Utama.

Buat

res/xml/news_widget_info.xml

Menentukan konfigurasi widget Layar Utama Anda. Anda dapat menyesuaikan dimensi atau nama widget dalam file ini.

Buat

java/com/example/homescreen_widgets/NewsWidget.kt

Berisi kode Kotlin untuk menambahkan fungsi ke widget Layar Utama Anda.

Anda dapat menemukan detail selengkapnya tentang file tersebut di seluruh codelab ini.

Men-debug dan menguji widget contoh Anda

Sekarang, jalankan aplikasi Anda dan lihat widget Layar Utama. Setelah membangun aplikasi, buka layar pemilihan aplikasi di perangkat Android Anda, lalu tekan lama ikon untuk project Flutter ini. Pilih Widgets dari menu pop-up.

dff7c9f9f85ef1c7.png

Perangkat Android atau emulator menampilkan widget Layar Utama default Anda untuk Android.

4. Mengirim data dari aplikasi Flutter ke widget Layar Utama Anda

Anda dapat menyesuaikan widget Layar Utama dasar yang Anda buat. Update widget Layar Utama untuk menampilkan judul dan ringkasan artikel berita. Screenshot berikut menampilkan contoh widget Layar Utama yang menampilkan judul dan ringkasan.

acb90343a3e51b6d.png

Untuk meneruskan data antara aplikasi dan widget Layar Utama, Anda perlu menulis Dart dan kode native. Bagian ini membagi proses ini menjadi tiga bagian:

  1. Menulis kode Dart di aplikasi Flutter yang dapat digunakan Android dan iOS
  2. Menambahkan fungsi iOS native
  3. Menambahkan fungsi Android native

Menggunakan Grup Aplikasi iOS

Untuk berbagi data antara aplikasi induk iOS dan ekstensi widget, kedua target harus berasal dari grup aplikasi yang sama. Untuk mempelajari grup aplikasi lebih lanjut, lihat dokumentasi grup aplikasi Apple.

Perbarui ID paket Anda:

Di Xcode, buka setelan target Anda. Di kotak Penandatanganan & Tab Kemampuan, pastikan ID paket dan tim Anda telah ditetapkan.

Tambahkan Grup Aplikasi ke kedua target Runner dan target NewsWidgetExtension di Xcode:

Pilih + Capability -> Grup Aplikasi dan tambahkan Grup Aplikasi baru. Ulangi untuk target Runner (aplikasi induk) dan target widget.

135e1a8c4652dac.pngS

Menambahkan kode Dart

Aplikasi iOS dan Android dapat berbagi data dengan aplikasi Flutter dalam beberapa cara yang berbeda.Untuk berkomunikasi dengan aplikasi ini, manfaatkan penyimpanan key/value lokal perangkat. iOS menyebut toko ini UserDefaults, dan Android menyebut toko ini SharedPreferences. Paket home_widget menggabungkan API ini untuk menyederhanakan penyimpanan data ke salah satu platform dan memungkinkan widget Layar Utama mengambil data yang diperbarui.

707ae86f6650ac55.pngS

Data judul dan deskripsi berasal dari file news_data.dart. File ini berisi data tiruan dan class data 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,
  });
}

Memperbarui nilai judul dan deskripsi

Untuk menambahkan fungsi guna mengupdate widget Layar Utama dari aplikasi Flutter, buka file lib/home_screen.dart. Ganti konten file dengan kode berikut. Kemudian, ganti <YOUR APP GROUP> dengan ID untuk Grup Aplikasi Anda.

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

Fungsi updateHeadline menyimpan key-value pair ke penyimpanan lokal perangkat. Kunci headline_title menyimpan nilai newHeadline.title. Kunci headline_description menyimpan nilai newHeadline.description. Fungsi ini juga memberi tahu platform native bahwa data baru untuk widget Layar Utama dapat diambil dan dirender.

Mengubah floatingActionButton

Panggil fungsi updateHeadline saat floatingActionButton ditekan seperti yang ditunjukkan:

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

Dengan perubahan ini, saat pengguna menekan tombol Perbarui Judul dari halaman artikel, detail widget Layar Utama akan diperbarui.

Perbarui kode iOS untuk menampilkan data artikel

Untuk memperbarui widget Layar Utama di iOS, gunakan Xcode.

Buka file NewsWidgets.swift di Xcode:

Konfigurasikan TimelineEntry.

Ganti struct SimpleEntry dengan kode berikut:

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
}

Struktur NewsArticleEntry ini menentukan data yang masuk yang akan diteruskan ke widget Layar Utama saat diperbarui. Jenis TimelineEntry memerlukan parameter tanggal.Untuk mempelajari protokol TimelineEntry lebih lanjut, lihat dokumentasi LinimasaEntry Apple.

Edit View yang menampilkan konten

Ubah widget Layar Utama untuk menampilkan judul dan deskripsi artikel berita, bukan tanggal. Untuk menampilkan teks di SwiftUI, gunakan tampilan Text. Untuk menumpuk tampilan di atas satu sama lain di SwiftUI, gunakan tampilan VStack.

Ganti tampilan NewsWidgetEntryView yang dihasilkan dengan kode berikut:

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

Edit penyedia untuk memberi tahu widget Layar Utama kapan dan bagaimana cara mengupdate

Ganti Provider yang sudah ada dengan kode berikut. Kemudian, ganti ID grup aplikasi Anda dengan <YOUR APP GROUP>:

ios/NewsWidgets/NewsWidgets.swift

struct Provider: TimelineProvider {

// Placeholder is used as a placeholder when the widget is first displayed
    func placeholder(in context: Context) -> NewsArticleEntry {
//      Add some placeholder title and description, and get the current date
      NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
    }

// Snapshot entry represents the current time and state
    func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
      let entry: NewsArticleEntry
      if context.isPreview{
        entry = placeholder(in: context)
      }
      else{
        //      Get the data from the user defaults to display
        let userDefaults = UserDefaults(suiteName: <YOUR APP GROUP>)
        let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
        let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
        entry = NewsArticleEntry(date: Date(), title: title, description: description)
      }
        completion(entry)
    }

//    getTimeline is called for the current and optionally future times to update the widget
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
//      This just uses the snapshot function you defined earlier
      getSnapshot(in: context) { (entry) in
// atEnd policy tells widgetkit to request a new entry after the date has passed
        let timeline = Timeline(entries: [entry], policy: .atEnd)
                  completion(timeline)
              }
    }
}

Provider dalam kode sebelumnya sesuai dengan TimelineProvider. Provider memiliki tiga metode yang berbeda:

  1. Metode placeholder menghasilkan entri placeholder saat pengguna pertama kali melihat pratinjau widget Layar Utama.

45a0f64240c12efe.pngS

  1. Metode getSnapshot membaca data dari default pengguna dan membuat entri untuk waktu saat ini.
  2. Metode getTimeline menampilkan entri linimasa. Hal ini berguna saat Anda memiliki waktu yang tepat untuk memperbarui konten. Codelab ini menggunakan fungsi getSnapshot untuk mendapatkan status saat ini. Metode .atEnd memberi tahu widget Layar Utama untuk memuat ulang data setelah waktu saat ini berlalu.

Jadikan NewsWidgets_Previews sebagai komentar

Penggunaan pratinjau berada di luar cakupan codelab ini. Untuk detail selengkapnya tentang melihat pratinjau widget Layar Utama SwiftUI, lihat Dokumentasi Apple tentang Widget Debugging.

Simpan semua file dan jalankan kembali target aplikasi dan widget.

Jalankan lagi target untuk memvalidasi bahwa aplikasi dan widget Layar Utama berfungsi.

  1. Pilih skema aplikasi di Xcode untuk menjalankan target aplikasi.
  2. Pilih skema ekstensi di Xcode untuk menjalankan target ekstensi.
  3. Buka halaman artikel di aplikasi.
  4. Klik tombol untuk memperbarui judul. Widget Layar Utama juga akan memperbarui judul.

Mengupdate kode Android

Tambahkan XML widget Layar Utama.

Di Android Studio, update file yang dihasilkan di langkah sebelumnya.Buka file res/layout/news_widget.xml. Kode ini menentukan struktur dan tata letak widget layar utama Anda. Pilih Code di sudut kanan atas dan ganti konten file tersebut dengan kode berikut:

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>

XML ini mendefinisikan dua tampilan teks, satu untuk judul artikel dan yang lain untuk deskripsi artikel. Tampilan teks ini juga menentukan gaya. Anda akan kembali ke file tersebut di seluruh codelab ini.

Mengupdate fungsi NewsWidget

Buka file kode sumber Kotlin NewsWidget.kt. File ini berisi class yang dihasilkan bernama NewsWidget yang memperluas class AppWidgetProvider.

Class NewsWidget berisi tiga metode dari superclass-nya. Anda akan mengubah metode onUpdate. Android memanggil metode ini untuk widget pada interval tetap.

Ganti konten file NewsWidget.kt dengan kode berikut:

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

Sekarang, saat onUpdate dipanggil, Android akan mendapatkan nilai terbaru dari penyimpanan lokal menggunakan metode the widgetData.getString(), lalu memanggil setTextViewText untuk mengubah teks yang ditampilkan di widget Layar Utama.

Menguji update

Uji aplikasi untuk memastikan widget Layar Utama Anda diperbarui dengan data baru. Untuk memperbarui data, gunakan Perbarui Layar Utama FloatingActionButton di halaman artikel. Widget Layar Utama Anda akan diperbarui dengan judul artikel.

5ce1c9914b43ad79.pngS

5. Menggunakan font kustom aplikasi Flutter di widget Layar Utama iOS

Sejauh ini, Anda telah mengonfigurasi widget Layar Utama untuk membaca data yang disediakan aplikasi Flutter. Aplikasi Flutter menyertakan font kustom yang mungkin ingin Anda gunakan di widget Layar Utama. Anda dapat menggunakan font kustom di widget Layar Utama iOS. Penggunaan font kustom di widget Layar Utama tidak tersedia di Android.

Mengupdate kode iOS

Flutter menyimpan asetnya di mainBundle aplikasi iOS. Anda dapat mengakses aset dalam paket ini dari kode widget Layar Utama.

Di struct NewsWidgetsEntryView di file NewsWidgets.swift Anda, buat perubahan berikut

Buat fungsi bantuan untuk mendapatkan jalur ke direktori aset 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
       }
   ...
}

Daftarkan font menggunakan URL ke file font kustom Anda.

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

Memperbarui tampilan Teks judul untuk menggunakan font kustom.

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

Saat Anda menjalankan widget Layar Utama, widget tersebut menggunakan font kustom untuk judul seperti yang ditampilkan dalam gambar berikut:

93f8b9d767aacfb2.pngS

6. Merender widget Flutter sebagai gambar

Di bagian ini, Anda akan menampilkan grafik dari aplikasi Flutter sebagai widget Layar Utama.

Widget ini memberikan tantangan yang lebih besar daripada teks yang Anda tampilkan di layar utama. Jauh lebih mudah untuk menampilkan diagram Flutter sebagai gambar daripada mencoba membuatnya kembali menggunakan komponen UI native.

Kodekan widget Layar Utama untuk merender diagram Flutter Anda sebagai file PNG. Widget Layar Utama Anda dapat menampilkan gambar tersebut.

Menulis kode Dart

Di sisi Dart, tambahkan metode renderFlutterWidget dari paket home_widget. Metode ini mengambil widget, nama file, dan kunci. Fungsi ini menampilkan gambar widget Flutter dan menyimpannya ke container bersama. Berikan nama image dalam kode Anda dan pastikan widget Layar Utama dapat mengakses penampung. key menyimpan jalur file lengkap sebagai string dalam penyimpanan lokal perangkat. Tindakan ini memungkinkan widget Layar Utama menemukan file jika namanya berubah dalam kode Dart.

Untuk codelab ini, class LineChart di file lib/article_screen.dart mewakili diagram. Metode build-nya menampilkan CustomPainter yang melukiskan diagram ini ke layar.

Untuk menerapkan fitur ini, buka file lib/article_screen.dart. Impor paket home_widget. Selanjutnya, ganti kode di class _ArticleScreenState dengan kode berikut:

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

Contoh ini membuat tiga perubahan pada class _ArticleScreenState.

Membuat GlobalKey

GlobalKey mendapatkan konteks dari widget tertentu, yang diperlukan untuk mendapatkan ukuran widget tersebut .

lib/article_screen.dart

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

Menambahkan imagePath

Properti imagePath menyimpan lokasi gambar tempat widget Flutter dirender.

lib/article_screen.dart

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

Menambahkan kunci ke widget untuk dirender

_globalKey berisi widget Flutter yang dirender ke gambar. Dalam hal ini, widget Flutter adalah Pusat yang berisi LineChart.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. Menyimpan widget sebagai gambar

Metode renderFlutterWidget dipanggil saat pengguna mengklik floatingActionButton. Metode ini menyimpan file PNG yang dihasilkan sebagai "screenshot" ke direktori container bersama. Metode ini juga menyimpan jalur lengkap ke gambar sebagai kunci nama file dalam penyimpanan perangkat.

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

Memperbarui kode iOS

Untuk iOS, perbarui kode guna mendapatkan jalur file dari penyimpanan dan menampilkan file sebagai gambar menggunakan SwiftUI.

Buka file NewsWidgets.swift untuk membuat perubahan berikut:

Tambahkan filename dan displaySize ke struct NewsArticleEntry

Properti filename menyimpan string yang mewakili jalur ke file gambar. Properti displaySize menyimpan ukuran widget Layar Utama di perangkat pengguna. Ukuran widget Layar Utama berasal dari context.

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

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

Mengupdate fungsi placeholder

Menyertakan placeholder filename dan 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)
    }

Dapatkan nama file dari userDefaults di getSnapshot

Tindakan ini menetapkan variabel filename ke nilai filename di penyimpanan userDefaults saat widget Layar Utama diupdate.

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

Buat ChartImage yang menampilkan gambar dari jalur

Tampilan ChartImage membuat gambar dari konten file yang dibuat di sisi Dart. Di sini, Anda menetapkan ukuran ke 50% dari bingkai.

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

Gunakan ChartImage di isi NewsWidgetsEntryView

Tambahkan tampilan ChartImage ke isi NewsWidgetsEntryView untuk menampilkan ChartImage di widget Layar Utama.

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
}

Menguji perubahan

Untuk menguji perubahan, jalankan kembali target aplikasi Flutter (Runner) dan target ekstensi Anda dari Xcode. Untuk melihat gambar, buka salah satu halaman artikel di aplikasi dan tekan tombol untuk mengupdate widget Layar Utama.

33bdfe2cce908c48.pngS

Memperbarui kode Android

Kode Android berfungsi sama dengan kode iOS.

  1. Buka file android/app/res/layout/news_widget.xml. Widget ini berisi elemen UI widget Layar Utama Anda. Ganti kontennya dengan kode berikut:

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>

Kode baru ini menambahkan gambar ke widget Layar Utama, yang (untuk saat ini) menampilkan ikon bintang generik. Ganti ikon bintang ini dengan gambar yang Anda simpan di kode Dart.

  1. Buka file NewsWidget.kt. Ganti kontennya dengan kode berikut:

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

Kode Dart ini menyimpan screenshot ke penyimpanan lokal dengan kunci filename. Kode ini juga mendapatkan jalur lengkap gambar dan membuat objek File darinya. Jika gambar ada, kode Dart akan mengganti gambar di widget Layar Utama dengan gambar baru.

  1. Muat ulang aplikasi Anda dan buka layar artikel. Tekan Perbarui Layar Utama. Widget Layar Utama menampilkan diagram.

7. Langkah Berikutnya

Selamat!

Selamat, Anda berhasil membuat widget Layar Utama untuk aplikasi Flutter iOS dan Android.

Menautkan ke konten di aplikasi Flutter Anda

Anda mungkin ingin mengarahkan pengguna ke halaman tertentu di aplikasi, bergantung pada tempat pengguna mengkliknya. Misalnya, di aplikasi berita dari codelab ini, Anda mungkin ingin pengguna melihat artikel berita untuk judul yang ditampilkan.

Fitur ini berada di luar cakupan codelab ini. Anda dapat menemukan contoh penggunaan streaming yang disediakan paket home_widget untuk mengidentifikasi peluncuran aplikasi dari widget Layar Utama dan mengirim pesan dari widget Layar Utama melalui URL. Untuk mempelajari lebih lanjut, lihat dokumentasi deep linking di docs.flutter.dev.

Mengupdate widget di latar belakang

Dalam codelab ini, Anda memicu pembaruan widget Layar Utama menggunakan tombol. Meskipun wajar untuk pengujian, dalam kode produksi, Anda mungkin ingin aplikasi mengupdate widget Layar Utama di latar belakang. Anda dapat menggunakan plugin workmanager untuk membuat tugas latar belakang guna memperbarui resource yang diperlukan widget Layar Utama. Untuk mempelajari lebih lanjut, lihat bagian Pembaruan latar belakang di paket home_widget.

Untuk iOS, Anda juga dapat meminta widget Layar Utama membuat permintaan jaringan untuk mengupdate UI-nya. Untuk mengontrol kondisi atau frekuensi permintaan tersebut, gunakan Linimasa. Untuk mempelajari lebih lanjut cara menggunakan Linimasa, lihat "Menjaga widget selalu diperbarui" dari Apple dokumentasi tambahan.

Bacaan lebih lanjut