การเพิ่มวิดเจ็ตหน้าจอหลักลงในแอป Flutter

1. บทนำ

วิดเจ็ตคืออะไร

สำหรับนักพัฒนาซอฟต์แวร์ Flutter คำจำกัดความทั่วไปของวิดเจ็ตจะหมายถึงคอมโพเนนต์ UI ที่สร้างขึ้นโดยใช้เฟรมเวิร์ก Flutter ในบริบทของ Codelab นี้ วิดเจ็ตหมายถึงแอปเวอร์ชันขนาดเล็กที่ให้มุมมองข้อมูลของแอปโดยไม่ต้องเปิดแอป สำหรับ Android วิดเจ็ตจะแสดงบนหน้าจอหลัก บน iOS คุณสามารถเพิ่มการ์ดลงในหน้าจอหลัก หน้าจอล็อก หรือมุมมองวันนี้

f0027e8a7d0237e0.png b991e79ea72c8b65.png

วิดเจ็ตจะซับซ้อนเพียงใด

วิดเจ็ตหน้าจอหลักส่วนใหญ่ใช้งานง่าย ซึ่งอาจมีข้อความพื้นฐาน กราฟิกง่ายๆ หรือการควบคุมขั้นพื้นฐานใน Android ทั้ง Android และ iOS จำกัดองค์ประกอบและฟีเจอร์ของ UI ที่ใช้ได้

819b9fffd700e571.png 92d62ccfd17d770d.png

สร้าง UI สำหรับวิดเจ็ต

เนื่องจากข้อจำกัดของ UI เหล่านี้ คุณจึงวาด UI ของวิดเจ็ตหน้าจอหลักโดยตรงโดยใช้เฟรมเวิร์ก Flutter ไม่ได้ คุณเพิ่มวิดเจ็ตที่สร้างด้วยเฟรมเวิร์กของแพลตฟอร์ม เช่น Jetpack Compose หรือ SwiftUI ไปยังแอป Flutter แทนได้ Codelab นี้จะกล่าวถึงตัวอย่างสำหรับการแชร์ทรัพยากรระหว่างแอปและวิดเจ็ตเพื่อหลีกเลี่ยงการเขียน UI ใหม่ที่ซับซ้อน

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะได้สร้างวิดเจ็ตหน้าจอหลักทั้งใน Android และ iOS สำหรับแอป Flutter แบบง่ายๆ โดยใช้แพ็กเกจ home_widget ที่อนุญาตให้ผู้ใช้อ่านบทความ วิดเจ็ตของคุณจะ:

  • แสดงข้อมูลจากแอป Flutter
  • แสดงข้อความโดยใช้เนื้อหาแบบอักษรที่แชร์จากแอป Flutter
  • แสดงรูปภาพของวิดเจ็ต Flutter ที่แสดงผล

a36b7ba379151101.png

แอป Flutter นี้มี 2 หน้าจอ (หรือเส้นทาง) ดังนี้

  • แบบแรกจะแสดงรายการบทความข่าวพร้อมพาดหัวและคำอธิบาย
  • รายการที่ 2 แสดงบทความฉบับเต็มพร้อมแผนภูมิที่สร้างด้วย CustomPaint

.

9c02f8b62c1faa3a.png d97d44051304cae4.png

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างวิดเจ็ตบนหน้าจอหลักใน iOS และ Android
  • วิธีใช้แพ็กเกจ home_Widget เพื่อแชร์ข้อมูลระหว่างวิดเจ็ต Home Screen กับแอป Flutter
  • วิธีลดจำนวนโค้ดที่ต้องเขียนใหม่
  • วิธีอัปเดตวิดเจ็ตหน้าจอหลักจากแอป Flutter

2. ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

คุณต้องมี Flutter SDK และ IDE สำหรับทั้ง 2 แพลตฟอร์ม คุณใช้ IDE ที่ต้องการสำหรับการทำงานร่วมกับ Flutter ได้ ซึ่งอาจเป็นโค้ด Visual Studio ที่มีส่วนขยาย Dart Code และ Flutter หรือ Android Studio หรือ IntelliJ ที่มีปลั๊กอิน Flutter และ Dart ติดตั้งอยู่

วิธีสร้างวิดเจ็ตหน้าจอหลักของ iOS

  • คุณเรียกใช้ Codelab นี้ในอุปกรณ์ iOS จริงหรือเครื่องจำลอง iOS ได้
  • คุณต้องกำหนดค่าระบบ macOS ด้วย Xcode IDE การดำเนินการนี้จะติดตั้งคอมไพเลอร์ที่ต้องใช้ในการสร้างแอปเวอร์ชัน iOS

วิธีสร้างวิดเจ็ตหน้าจอหลักของ Android

  • คุณสามารถเรียกใช้ Codelab นี้ในอุปกรณ์ Android จริงหรือโปรแกรมจำลอง Android ได้
  • คุณต้องกำหนดค่าระบบการพัฒนาด้วย Android Studio การดำเนินการนี้จะติดตั้งคอมไพเลอร์ที่ต้องใช้ในการสร้างแอปเวอร์ชัน Android

รับรหัสเริ่มต้น

ดาวน์โหลดเวอร์ชันเริ่มต้นของโปรเจ็กต์จาก GitHub

โคลนที่เก็บ GitHub จากบรรทัดคำสั่งลงในไดเรกทอรี flutter-codelabs

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

หลังจากโคลนที่เก็บแล้ว คุณสามารถดูโค้ดสำหรับ Codelab นี้ได้ในไดเรกทอรี flutter-codelabs/homescreen_codelab ไดเรกทอรีนี้มีรหัสโครงการที่เสร็จสมบูรณ์แล้วสำหรับแต่ละขั้นตอนใน Codelab

เปิดแอปเริ่มต้น

เปิดไดเรกทอรี flutter-codelabs/homescreen_codelab/step_03 ใน IDE ที่ต้องการ

ติดตั้งแพ็กเกจ

เพิ่มแพ็กเกจที่จำเป็นทั้งหมดลงในไฟล์ pubspec.yaml ของโปรเจ็กต์แล้ว หากต้องการเรียกข้อมูลทรัพยากร Dependency ของโปรเจ็กต์ ให้เรียกใช้คำสั่งต่อไปนี้

$ flutter pub get

3. เพิ่มวิดเจ็ตพื้นฐานในหน้าจอหลัก

ก่อนอื่น ให้เพิ่มวิดเจ็ต "หน้าจอหลัก" โดยใช้เครื่องมือของแพลตฟอร์มดั้งเดิม

การสร้างวิดเจ็ตพื้นฐานในหน้าจอหลักของ iOS

การเพิ่มส่วนขยายแอปลงในแอป Flutter iOS จะคล้ายกับการเพิ่มส่วนขยายแอปลงในแอป SwiftUI หรือ UIKit ดังนี้

  1. เรียกใช้ open ios/Runner.xcworkspace ในหน้าต่างเทอร์มินัลจากไดเรกทอรีโปรเจ็กต์ Flutter หรือคลิกขวาที่โฟลเดอร์ ios จาก VSCode แล้วเลือก เปิดใน Xcode การดำเนินการนี้จะเปิดพื้นที่ทำงาน Xcode เริ่มต้นในโปรเจ็กต์ Flutter ของคุณ
  2. เลือกไฟล์ → ใหม่ → เป้าหมายจากเมนู ซึ่งจะเป็นการเพิ่มเป้าหมายใหม่ในโปรเจ็กต์
  3. รายการเทมเพลตจะปรากฏขึ้น เลือกส่วนขยายวิดเจ็ต
  4. พิมพ์ "NewsWidgets" ลงในช่อง Product Name สำหรับวิดเจ็ตนี้ ล้างทั้งช่องทำเครื่องหมายรวมการถ่ายทอดสดและรวมความตั้งใจในการกำหนดค่า

ตรวจสอบโค้ดตัวอย่าง

เมื่อคุณเพิ่มเป้าหมายใหม่ Xcode จะสร้างโค้ดตัวอย่างตามเทมเพลตที่คุณเลือก สำหรับข้อมูลเพิ่มเติมเกี่ยวกับโค้ดที่สร้างขึ้นและ WidgetKit โปรดดูเอกสารส่วนขยายแอปของ Apple

แก้ไขข้อบกพร่องและทดสอบวิดเจ็ตตัวอย่าง

  1. ก่อนอื่นให้อัปเดตการกำหนดค่าของแอป Flutter คุณต้องทำเช่นนี้เมื่อเพิ่มแพ็กเกจใหม่ในแอป Flutter และวางแผนที่จะเรียกใช้เป้าหมายในโปรเจ็กต์จาก Xcode หากต้องการอัปเดตการกำหนดค่าของแอป ให้เรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรีแอป Flutter
$ flutter build ios --config-only
  1. คลิก Runner เพื่อแสดงรายการเป้าหมาย เลือกเป้าหมายวิดเจ็ตที่คุณเพิ่งสร้าง ไปที่ NewsWidgets แล้วคลิกเรียกใช้ เรียกใช้เป้าหมายวิดเจ็ตจาก Xcode เมื่อคุณเปลี่ยนโค้ดวิดเจ็ตของ iOS

bbb519df1782881d.png

  1. เครื่องจำลองหรือหน้าจออุปกรณ์ควรแสดงวิดเจ็ตพื้นฐานในหน้าจอหลัก หากไม่เห็นไอคอนนี้ก็เพิ่มลงในหน้าจอได้ คลิกหน้าจอหลักค้างไว้ แล้วคลิก + ที่มุมซ้ายบน

18eff1cae152014d.png

  1. ค้นหาชื่อแอป สำหรับ Codelab นี้ ให้ค้นหา "วิดเจ็ตหน้าจอหลัก"

a0c00df87615493e.png

  1. เมื่อคุณเพิ่มวิดเจ็ต "หน้าจอหลัก" แล้ว วิดเจ็ตจะแสดงข้อความง่ายๆ ที่ระบุเวลา

การสร้างวิดเจ็ต Android พื้นฐาน

  1. หากต้องการเพิ่มวิดเจ็ตหน้าจอหลักใน Android ให้เปิดไฟล์บิลด์ของโปรเจ็กต์ใน Android Studio หาไฟล์นี้ได้ที่ android/build.gradle หรือคลิกขวาที่โฟลเดอร์ android จาก VSCode แล้วเลือกเปิดใน Android Studio
  2. หลังจากสร้างโปรเจ็กต์แล้ว ให้ค้นหาไดเรกทอรีแอปที่มุมบนซ้าย เพิ่มวิดเจ็ตหน้าจอหลักใหม่ลงในไดเรกทอรีนี้ คลิกขวาที่ไดเรกทอรี เลือก New -> (ใหม่) วิดเจ็ต -> วิดเจ็ตแอป

f19d8b7f95ab884e.png

  1. Android Studio แสดงแบบฟอร์มใหม่ เพิ่มข้อมูลพื้นฐานเกี่ยวกับวิดเจ็ตหน้าจอหลัก ซึ่งรวมถึงชื่อคลาส ตำแหน่ง ขนาด และภาษาต้นฉบับ

สำหรับ Codelab นี้ ให้ตั้งค่าต่อไปนี้

  • ช่องชื่อชั้นเรียนไปยัง NewsWidget
  • เมนูแบบเลื่อนลงความกว้างขั้นต่ำ (เซลล์) เป็น 3
  • เมนูแบบเลื่อนลงความสูงขั้นต่ำ (เซลล์) เป็น 3

ตรวจสอบโค้ดตัวอย่าง

เมื่อคุณส่งแบบฟอร์ม Android Studio จะสร้างและอัปเดตไฟล์หลายไฟล์ การเปลี่ยนแปลงที่เกี่ยวข้องกับ Codelab จะแสดงอยู่ในตารางด้านล่าง

การดำเนินการ

ไฟล์เป้าหมาย

เปลี่ยน

อัปเดต

AndroidManifest.xml

เพิ่มตัวรับใหม่ที่ลงทะเบียน NewsWidget

สร้าง

res/layout/news_widget.xml

กำหนด UI ของวิดเจ็ตบนหน้าจอหลัก

สร้าง

res/xml/news_widget_info.xml

กำหนดการกำหนดค่าวิดเจ็ตบนหน้าจอหลัก คุณสามารถปรับขนาดหรือชื่อวิดเจ็ตได้ในไฟล์นี้

สร้าง

java/com/example/homescreen_widgets/NewsWidget.kt

มีโค้ด Kotlin เพื่อเพิ่มฟังก์ชันการทำงานลงในวิดเจ็ตหน้าจอหลัก

ดูรายละเอียดเพิ่มเติมเกี่ยวกับไฟล์เหล่านี้ได้ใน Codelab นี้

แก้ไขข้อบกพร่องและทดสอบวิดเจ็ตตัวอย่าง

จากนั้นเรียกใช้แอปพลิเคชันแล้วดูวิดเจ็ตหน้าจอหลัก เมื่อสร้างแอปแล้ว ให้ไปที่หน้าจอการเลือกแอปพลิเคชันของอุปกรณ์ Android แล้วกดไอคอนสำหรับโปรเจ็กต์ Flutter นี้ค้างไว้ เลือกวิดเจ็ตจากเมนูป๊อปอัป

dff7c9f9f85ef1c7.png

อุปกรณ์ Android หรือโปรแกรมจำลองจะแสดงวิดเจ็ตเริ่มต้นสำหรับ Android

4. ส่งข้อมูลจากแอป Flutter ไปยังวิดเจ็ตหน้าจอหลัก

คุณสามารถปรับแต่งวิดเจ็ตพื้นฐานที่คุณสร้างขึ้นได้ อัปเดตวิดเจ็ตหน้าจอหลักเพื่อแสดงพาดหัวและสรุปของบทความข่าว ภาพหน้าจอต่อไปนี้แสดงตัวอย่างวิดเจ็ตที่แสดงบรรทัดแรกและข้อมูลสรุป

acb90343a3e51b6d.png

หากต้องการส่งข้อมูลระหว่างแอปและวิดเจ็ตหน้าจอหลัก คุณต้องเขียนโค้ดด้วย Dart และที่มาพร้อมเครื่อง ส่วนนี้แบ่งออกเป็น 3 ส่วนดังนี้

  1. เขียนโค้ด Dart ในแอป Flutter ที่ทั้ง Android และ iOS ใช้ได้
  2. การเพิ่มฟังก์ชันดั้งเดิมของ iOS
  3. การเพิ่มฟังก์ชันดั้งเดิมของ Android

การใช้กลุ่มแอป iOS

หากต้องการแชร์ข้อมูลระหว่างแอประดับบนสุดบน iOS และส่วนขยายวิดเจ็ต เป้าหมายทั้ง 2 รายการต้องอยู่ในกลุ่มแอปเดียวกัน ดูข้อมูลเพิ่มเติมเกี่ยวกับกลุ่มแอปได้จากเอกสารประกอบเกี่ยวกับกลุ่มแอปของ Apple

อัปเดตตัวระบุกลุ่มโดยทำดังนี้

ไปที่การตั้งค่าของเป้าหมายใน Xcode ในส่วนการลงชื่อและ ความสามารถ ให้ตรวจสอบว่ามีการตั้งค่าตัวระบุทีมและชุดอุปกรณ์แล้ว

เพิ่มกลุ่มแอปลงในทั้งเป้าหมาย Runner และเป้าหมาย NewsWidgetExtension ใน Xcode ดังนี้

เลือก + ความสามารถ -> กลุ่มแอป แล้วเพิ่มกลุ่มแอปใหม่ ทำซ้ำสำหรับทั้งเป้าหมาย Runner (แอปหลัก) และเป้าหมายวิดเจ็ต

135e1a8c4652dac.png

เพิ่มโค้ด Dart

ทั้งแอป iOS และ Android จะแชร์ข้อมูลกับแอป Flutter ได้หลายวิธี หากต้องการสื่อสารกับแอปเหล่านี้ ให้ใช้ประโยชน์จาก Store ของ key/value ในอุปกรณ์ iOS เรียกร้านนี้ว่า UserDefaults และ Android เรียกร้านนี้ว่า SharedPreferences แพ็กเกจ home_Widget จะรวม API เหล่านี้เพื่อช่วยให้การบันทึกข้อมูลไปยังแพลตฟอร์มต่างๆ ง่ายขึ้น และช่วยให้วิดเจ็ตบนหน้าจอหลักสามารถดึงข้อมูลที่อัปเดตแล้วได้

707ae86f6650ac55.png

ข้อมูลบรรทัดแรกและคำอธิบายมาจากไฟล์ news_data.dart ไฟล์นี้มีข้อมูลจำลองและคลาสข้อมูล 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,
  });
}

อัปเดตค่าบรรทัดแรกและคำอธิบาย

หากต้องการเพิ่มฟังก์ชันการทำงานเพื่ออัปเดตวิดเจ็ตหน้าจอหลักจากแอป Flutter ให้ไปที่ไฟล์ lib/home_screen.dart แทนที่เนื้อหาของไฟล์ด้วยโค้ดต่อไปนี้ จากนั้นแทนที่ <YOUR APP GROUP> ด้วยตัวระบุสำหรับกลุ่มแอป

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

ฟังก์ชัน updateHeadline จะบันทึกคู่คีย์/ค่าลงในพื้นที่เก็บข้อมูลในเครื่องของอุปกรณ์ คีย์ headline_title มีค่า newHeadline.title คีย์ headline_description จะเก็บค่าของ newHeadline.description ฟังก์ชันนี้ยังแจ้งแพลตฟอร์มเนทีฟว่าสามารถเรียกดูและแสดงผลข้อมูลใหม่สำหรับวิดเจ็ตหน้าจอหลักได้

แก้ไขปุ่มการทำงานแบบลอย

เรียกใช้ฟังก์ชัน updateHeadline เมื่อกด floatingActionButton ตามที่แสดง

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

ด้วยการเปลี่ยนแปลงนี้ เมื่อผู้ใช้กดปุ่มอัปเดตพาดหัวจากหน้าบทความ ระบบจะอัปเดตรายละเอียดวิดเจ็ตบนหน้าจอหลัก

อัปเดตโค้ด iOS เพื่อแสดงข้อมูลบทความ

ใช้ Xcode เพื่ออัปเดตวิดเจ็ตหน้าจอหลักสำหรับ iOS

เปิดไฟล์ NewsWidgets.swift ใน Xcode:

กำหนดค่า TimelineEntry

แทนที่โครงสร้าง SimpleEntry ด้วยโค้ดต่อไปนี้

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
}

โครงสร้าง NewsArticleEntry นี้จะกำหนดข้อมูลที่เข้ามาซึ่งจะส่งผ่านไปยังวิดเจ็ตหน้าจอหลักเมื่อมีการอัปเดต ประเภท TimelineEntry ต้องมีพารามิเตอร์วันที่ ดูข้อมูลเพิ่มเติมเกี่ยวกับโปรโตคอล TimelineEntry ได้ที่เอกสารประกอบไทม์ไลน์ของ Apple

แก้ไข View ที่แสดงเนื้อหา

แก้ไขวิดเจ็ตบนหน้าจอหลักให้แสดงพาดหัวและคำอธิบายของบทความข่าวแทนที่การระบุวันที่ หากต้องการแสดงข้อความใน SwiftUI ให้ใช้มุมมอง Text หากต้องการเรียงมุมมองซ้อนกันใน SwiftUI ให้ใช้มุมมอง VStack

แทนที่มุมมอง NewsWidgetEntryView ที่สร้างขึ้นด้วยโค้ดต่อไปนี้

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

แก้ไขผู้ให้บริการว่าจะให้อัปเดตวิดเจ็ตหน้าจอหลักเมื่อใดและอย่างไร

แทนที่ Provider ที่มีอยู่ด้วยรหัสต่อไปนี้ จากนั้นแทนที่ตัวระบุกลุ่มแอปด้วย <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 ในโค้ดก่อนหน้านี้สอดคล้องกับ TimelineProvider Provider มี 3 วิธี ได้แก่

  1. เมธอด placeholder จะสร้างรายการตัวยึดตำแหน่งเมื่อผู้ใช้แสดงตัวอย่างวิดเจ็ตในหน้าจอหลักเป็นครั้งแรก

45a0f64240c12efe.png

  1. เมธอด getSnapshot จะอ่านข้อมูลจากค่าเริ่มต้นของผู้ใช้และสร้างรายการเวลาปัจจุบัน
  2. เมธอด getTimeline จะแสดงรายการไทม์ไลน์ วิธีนี้จะเป็นประโยชน์เมื่อคุณมีจุดที่คาดการณ์ได้ล่วงหน้าสำหรับการอัปเดตเนื้อหา Codelab นี้ใช้ฟังก์ชัน getSnapshot เพื่อรับสถานะปัจจุบันของ เมธอด .atEnd จะบอกให้วิดเจ็ตหน้าจอหลักรีเฟรชข้อมูลหลังจากเวลาปัจจุบันผ่านไป

แสดงความคิดเห็นเกี่ยวกับ NewsWidgets_Previews

การใช้ตัวอย่างอยู่นอกขอบเขตของ Codelab นี้ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการดูตัวอย่างวิดเจ็ตหน้าจอหลักของ SwiftUI โปรดดูเอกสารประกอบของ Apple เกี่ยวกับวิดเจ็ตการแก้ไขข้อบกพร่อง

บันทึกไฟล์ทั้งหมดและเรียกใช้เป้าหมายแอปและวิดเจ็ตอีกครั้ง

เรียกใช้เป้าหมายอีกครั้งเพื่อตรวจสอบว่าแอปและวิดเจ็ตหน้าจอหลักทำงานได้หรือไม่

  1. เลือกสคีมาของแอปใน Xcode เพื่อเรียกใช้เป้าหมายแอป
  2. เลือกสคีมาของส่วนขยายใน Xcode เพื่อเรียกใช้เป้าหมายส่วนขยาย
  3. ไปที่หน้าบทความในแอป
  4. คลิกปุ่มเพื่ออัปเดตพาดหัว วิดเจ็ตหน้าจอหลักควรอัปเดตบรรทัดแรกด้วย

อัปเดตโค้ด Android

เพิ่ม XML ของวิดเจ็ตหน้าจอหลัก

ใน Android Studio ให้อัปเดตไฟล์ที่สร้างขึ้นในขั้นตอนก่อนหน้า โดยเปิดไฟล์ res/layout/news_widget.xml ซึ่งจะกำหนดโครงสร้างและเลย์เอาต์ของวิดเจ็ตหน้าจอหลัก เลือกโค้ดที่มุมขวาบนและแทนที่เนื้อหาของไฟล์นั้นด้วยโค้ดต่อไปนี้

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 นี้กำหนดมุมมองข้อความ 2 มุมมอง คือมุมมองสำหรับพาดหัวบทความ และอีกมุมมองสำหรับคำอธิบายบทความ มุมมองข้อความเหล่านี้จะกำหนดการจัดรูปแบบด้วย คุณจะกลับมาที่ไฟล์นี้ตลอดทั้ง Codelab นี้

อัปเดตฟังก์ชันการทำงานของ NewsWidget

เปิดไฟล์ซอร์สโค้ด Kotlin ของ NewsWidget.kt ไฟล์นี้มีคลาสที่สร้างขึ้นชื่อว่า NewsWidget ที่ขยายคลาส AppWidgetProvider

คลาส NewsWidget มี 3 เมธอดจาก Superclass คุณจะต้องแก้ไขเมธอด onUpdate Android เรียกใช้วิธีการนี้สำหรับวิดเจ็ตในช่วงเวลาคงที่

แทนที่เนื้อหาของไฟล์ NewsWidget.kt ด้วยโค้ดต่อไปนี้

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

ตอนนี้เมื่อมีการเรียก onUpdate Android จะได้รับค่าล่าสุดจากพื้นที่เก็บข้อมูลในเครื่องโดยใช้เมธอด the widgetData.getString() จากนั้นจะเรียกใช้ setTextViewText เพื่อเปลี่ยนข้อความที่แสดงในวิดเจ็ตหน้าจอหลัก

ทดสอบการอัปเดต

ทดสอบแอปเพื่อให้แน่ใจว่าวิดเจ็ตหน้าจอหลักจะอัปเดตด้วยข้อมูลใหม่ หากต้องการอัปเดตข้อมูล ให้ใช้อัปเดตหน้าจอหลัก FloatingActionButton ในหน้าบทความ วิดเจ็ตบนหน้าจอหลักควรอัปเดตด้วยชื่อบทความ

5ce1c9914b43ad79.png

5. การใช้แบบอักษรที่กำหนดเองของแอป Flutter ในวิดเจ็ตหน้าจอหลักของ iOS

จนถึงตอนนี้ คุณได้กำหนดค่าวิดเจ็ตหน้าจอหลักให้อ่านข้อมูลที่แอป Flutter มีให้ แอป Flutter มีแบบอักษรที่กำหนดเองซึ่งคุณอาจต้องใช้ในวิดเจ็ตหน้าจอหลัก คุณสามารถใช้แบบอักษรที่กำหนดเองได้ในวิดเจ็ตหน้าจอหลักของ iOS การใช้แบบอักษรที่กำหนดเองในวิดเจ็ตหน้าจอหลักยังไม่พร้อมใช้งานบน Android

อัปเดตโค้ด iOS

Flutter จะจัดเก็บเนื้อหาไว้ใน mainBundle ของแอปพลิเคชัน iOS คุณเข้าถึงเนื้อหาในแพ็กเกจนี้ได้จากรหัสวิดเจ็ตในหน้าจอหลัก

ในโครงสร้าง NewsWidgetsEntryView ในไฟล์ NewsWidgets.swift ให้ทำการเปลี่ยนแปลงต่อไปนี้

สร้างฟังก์ชันตัวช่วยเพื่อรับเส้นทางไปยังไดเรกทอรีเนื้อหา 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
       }
   ...
}

ลงทะเบียนแบบอักษรโดยใช้ URL ไปยังไฟล์แบบอักษรที่กำหนดเอง

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

อัปเดตมุมมองข้อความบรรทัดแรกเพื่อใช้แบบอักษรที่กำหนดเอง

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

เมื่อคุณเรียกใช้วิดเจ็ตหน้าจอหลัก วิดเจ็ตจะใช้แบบอักษรที่กำหนดเองสำหรับบรรทัดแรกดังที่แสดงในรูปภาพต่อไปนี้

93f8b9d767aacfb2.png

6. การแสดงผลวิดเจ็ต Flutter เป็นรูปภาพ

ในส่วนนี้คุณจะเห็นกราฟจากแอป Flutter เป็นวิดเจ็ตหน้าจอหลัก

วิดเจ็ตนี้สร้างความท้าทายได้มากกว่าข้อความที่คุณแสดงบนหน้าจอหลัก การแสดงแผนภูมิ Flutter เป็นรูปภาพจะง่ายกว่าการพยายามสร้างแผนภูมิโดยใช้คอมโพเนนต์ UI แบบดั้งเดิม

เขียนโค้ดวิดเจ็ตบนหน้าจอหลักเพื่อแสดงผลแผนภูมิ Flutter เป็นไฟล์ PNG วิดเจ็ตของหน้าจอหลักจะแสดงรูปภาพนั้นได้

เขียนโค้ดของ Dart

ในฝั่ง Dart ให้เพิ่มเมธอด renderFlutterWidget จากแพ็กเกจ home_Widget วิธีนี้ใช้วิดเจ็ต ชื่อไฟล์ และคีย์ ระบบจะแสดงรูปภาพของวิดเจ็ต Flutter และบันทึกไว้ในคอนเทนเนอร์ที่แชร์ ระบุชื่อรูปภาพในโค้ดและตรวจสอบว่าวิดเจ็ตหน้าจอหลักเข้าถึงคอนเทนเนอร์ได้ key จะบันทึกเส้นทางแบบเต็มของไฟล์เป็นสตริงในพื้นที่เก็บข้อมูลในเครื่องของอุปกรณ์ วิธีนี้จะช่วยให้วิดเจ็ตหน้าจอหลักค้นหาไฟล์ได้หากมีการเปลี่ยนชื่อในโค้ด Dart

สำหรับ Codelab นี้ คลาส LineChart ในไฟล์ lib/article_screen.dart จะแสดงแผนภูมินี้ เมธอดของบิลด์จะแสดง CustomPainter ที่ลงสีแผนภูมินี้บนหน้าจอ

หากต้องการใช้ฟีเจอร์นี้ ให้เปิดไฟล์ lib/article_screen.dart นำเข้าแพ็กเกจ home_widget จากนั้นแทนที่รหัสในชั้นเรียน _ArticleScreenState ด้วยรหัสต่อไปนี้

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

ตัวอย่างนี้ทำการเปลี่ยนแปลง 3 รายการกับชั้นเรียน _ArticleScreenState

สร้าง GlobalKey

GlobalKey จะได้รับบริบทของวิดเจ็ตนั้นๆ ซึ่งจำเป็นต้องใช้เพื่อดูขนาดของวิดเจ็ตนั้น

lib/article_screen.dart

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

เพิ่ม imagePath

พร็อพเพอร์ตี้ imagePath จะจัดเก็บตำแหน่งของรูปภาพที่แสดงวิดเจ็ต Flutter

lib/article_screen.dart

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

เพิ่มคีย์ลงในวิดเจ็ตเพื่อแสดงผล

_globalKey มีวิดเจ็ต Flutter ที่แสดงผลในรูปภาพ ในกรณีนี้ วิดเจ็ต Flutter คือศูนย์กลางที่มี LineChart

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. บันทึกวิดเจ็ตเป็นรูปภาพ

ระบบจะเรียกเมธอด renderFlutterWidget เมื่อผู้ใช้คลิกที่ floatingActionButton เมธอดบันทึกไฟล์ PNG ที่ได้เป็น "ภาพหน้าจอ" ลงในไดเรกทอรีคอนเทนเนอร์ที่แชร์ วิธีนี้จะบันทึกเส้นทางแบบเต็มไปยังรูปภาพเป็นคีย์ชื่อไฟล์ในพื้นที่เก็บข้อมูลของอุปกรณ์ด้วย

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

สำหรับ iOS ให้อัปเดตโค้ดเพื่อรับเส้นทางของไฟล์จากพื้นที่เก็บข้อมูล และแสดงไฟล์เป็นรูปภาพโดยใช้ SwiftUI

เปิดไฟล์ NewsWidgets.swift เพื่อทำการเปลี่ยนแปลงต่อไปนี้

เพิ่ม filename และ displaySize ในโครงสร้าง NewsArticleEntry

พร็อพเพอร์ตี้ filename จะมีสตริงที่แสดงถึงเส้นทางไปยังไฟล์ภาพ พร็อพเพอร์ตี้ displaySize จะมีขนาดวิดเจ็ตบนหน้าจอหลักในอุปกรณ์ของผู้ใช้ ขนาดของวิดเจ็ตหน้าจอหลักมาจาก context

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

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

อัปเดตฟังก์ชัน placeholder

รวมตัวยึดตำแหน่ง filename และ 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)
    }

รับชื่อไฟล์จาก userDefaults ใน getSnapshot

ซึ่งจะตั้งค่าตัวแปร filename เป็นค่า filename ในพื้นที่เก็บข้อมูล userDefaults เมื่อวิดเจ็ตหน้าจอหลักอัปเดต

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 จะสร้างรูปภาพจากเนื้อหาของไฟล์ที่สร้างขึ้นในฝั่ง Dart คุณตั้งขนาดเป็น 50% ของเฟรม

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 ในเนื้อหา NewsWidgetsEntryView

เพิ่มมุมมอง ChartImage ลงในเนื้อหาของ NewsWidgetsEntryView เพื่อแสดง ChartImage ในวิดเจ็ตหน้าจอหลัก

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
}

ทดสอบการเปลี่ยนแปลง

หากต้องการทดสอบการเปลี่ยนแปลง ให้เรียกใช้ทั้งเป้าหมายแอป Flutter (Runner) และเป้าหมายส่วนขยายจาก Xcode อีกครั้ง หากต้องการดูรูปภาพ ให้ไปที่หน้าบทความหน้าใดหน้าหนึ่งในแอป และกดปุ่มเพื่ออัปเดตวิดเจ็ตหน้าจอหลัก

33bdfe2cce908c48.png

อัปเดตโค้ด Android

โค้ด Android ทำงานเหมือนกับโค้ด iOS

  1. เปิดไฟล์ android/app/res/layout/news_widget.xml ซึ่งประกอบด้วยองค์ประกอบ UI ของวิดเจ็ตหน้าจอหลัก แทนที่เนื้อหาด้วยโค้ดต่อไปนี้

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>

รหัสใหม่นี้จะเพิ่มรูปภาพลงในวิดเจ็ตหน้าจอหลัก ซึ่ง (สำหรับตอนนี้) จะแสดงไอคอนรูปดาวทั่วไป แทนที่ไอคอนรูปดาวนี้ด้วยรูปภาพที่คุณบันทึกไว้ในโค้ดของ Dart

  1. เปิดไฟล์ NewsWidget.kt แทนที่เนื้อหาด้วยโค้ดต่อไปนี้

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

โค้ดของ Dart นี้จะบันทึกภาพหน้าจอลงในพื้นที่เก็บข้อมูลในเครื่องด้วยคีย์ filename นอกจากนี้ยังได้รับเส้นทางแบบเต็มของรูปภาพและสร้างออบเจ็กต์ File จากรูปภาพดังกล่าวด้วย หากมีรูปภาพอยู่ รหัสของ Dart จะแทนที่รูปภาพในวิดเจ็ตหน้าจอหลักด้วยรูปภาพใหม่

  1. โหลดแอปของคุณซ้ำและไปยังหน้าจอบทความ กดอัปเดตหน้าจอหลัก วิดเจ็ตหน้าจอหลักจะแสดงแผนภูมิ

7. ขั้นตอนถัดไป

ยินดีด้วย

ยินดีด้วย คุณสร้างวิดเจ็ตหน้าจอหลักสำหรับแอป Flutter ใน iOS และ Android สำเร็จแล้ว

การลิงก์ไปยังเนื้อหาในแอป Flutter

คุณอาจต้องการนําผู้ใช้ไปยังหน้าที่เจาะจงในแอป ทั้งนี้ขึ้นอยู่กับตําแหน่งที่ผู้ใช้คลิก ตัวอย่างเช่น ในแอปข่าวจาก Codelab นี้ คุณอาจต้องการให้ผู้ใช้เห็นบทความข่าวสำหรับพาดหัวที่แสดงอยู่

ฟีเจอร์นี้อยู่นอกขอบเขตของ Codelab นี้ คุณสามารถดูตัวอย่างการใช้สตรีมที่แพ็กเกจ home_Widget มีให้เพื่อระบุการเรียกใช้แอปจากวิดเจ็ตหน้าจอหลักและส่งข้อความจากวิดเจ็ตหน้าจอหลักผ่าน URL ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบการทำ Deep Link ใน docs.flutter.dev

การอัปเดตวิดเจ็ตในเบื้องหลัง

ใน Codelab นี้ คุณได้ทริกเกอร์การอัปเดตวิดเจ็ตบนหน้าจอหลักโดยใช้ปุ่ม แม้ว่าวิธีนี้จะสมเหตุสมผลสำหรับการทดสอบ แต่ในโค้ดเวอร์ชันที่ใช้งานจริง คุณอาจต้องการให้แอปอัปเดตวิดเจ็ตหน้าจอหลักในเบื้องหลัง คุณสามารถใช้ปลั๊กอิน Workmanager เพื่อสร้างงานเบื้องหลังเพื่ออัปเดตทรัพยากรที่วิดเจ็ตหน้าจอหลักต้องใช้ได้ หากต้องการเรียนรู้เพิ่มเติม ให้ดูที่ส่วนการอัปเดตพื้นหลังในแพ็กเกจ home_widget

สำหรับ iOS คุณสามารถใช้วิดเจ็ตบนหน้าจอหลักส่งคำขอเครือข่ายเพื่ออัปเดต UI ของตนได้ด้วย หากต้องการควบคุมเงื่อนไขหรือความถี่ของคำขอนั้น ให้ใช้ไทม์ไลน์ หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ไทม์ไลน์ โปรดดู "การอัปเดตวิดเจ็ตอยู่เสมอ" ของ Apple เอกสารประกอบ

อ่านเพิ่มเติม