افزودن ویجت صفحه اصلی به برنامه Flutter خود

۱. مقدمه

ویجت‌ها (ابزارک‌ها) چیستند؟

برای توسعه‌دهندگان فلاتر، تعریف رایج ویجت به اجزای رابط کاربری ایجاد شده با استفاده از چارچوب فلاتر اشاره دارد. در متن این آزمایشگاه کد، یک ویجت به نسخه کوچکی از یک برنامه اشاره دارد که بدون باز کردن برنامه، امکان مشاهده اطلاعات برنامه را فراهم می‌کند. در اندروید، ویجت‌ها روی صفحه اصلی قرار دارند. در iOS، می‌توان آنها را به صفحه اصلی، صفحه قفل یا نمای امروز اضافه کرد.

f0027e8a7d0237e0.pngb991e79ea72c8b65.png

یک ویجت چقدر می‌تواند پیچیده باشد؟

بیشتر ویجت‌های صفحه اصلی ساده هستند. آن‌ها ممکن است شامل متن ساده، گرافیک ساده یا در اندروید، کنترل‌های اولیه باشند. هم اندروید و هم iOS اجزا و ویژگی‌های رابط کاربری قابل استفاده را محدود می‌کنند.

819b9fffd700e571.png۹۲d62ccfd17d770d.png

ایجاد رابط کاربری برای ابزارک‌ها

به دلیل این محدودیت‌های رابط کاربری، شما نمی‌توانید مستقیماً رابط کاربری یک ویجت صفحه اصلی را با استفاده از چارچوب Flutter ترسیم کنید. در عوض، می‌توانید ویجت‌های ایجاد شده با چارچوب‌های پلتفرم مانند Jetpack Compose یا SwiftUI را به برنامه Flutter خود اضافه کنید. این codelab مثال‌هایی را برای اشتراک‌گذاری منابع بین برنامه شما و ویجت‌ها برای جلوگیری از بازنویسی رابط کاربری پیچیده مورد بحث قرار می‌دهد.

آنچه خواهید ساخت

در این آزمایشگاه کد، شما با استفاده از پکیج home_widget، ویجت‌های صفحه اصلی را هم در اندروید و هم در iOS برای یک برنامه ساده Flutter خواهید ساخت که به کاربران امکان خواندن مقالات را می‌دهد. ویجت‌های شما:

  • نمایش داده‌ها از برنامه Flutter شما.
  • متن را با استفاده از فونت‌های به اشتراک گذاشته شده از برنامه Flutter نمایش دهید.
  • تصویری از یک ویجت رندر شده‌ی Flutter را نمایش می‌دهد.

a36b7ba379151101.png

این برنامه Flutter شامل دو صفحه (یا مسیر ) است:

  • اولی فهرستی از مقالات خبری را به همراه تیترها و توضیحات نمایش می‌دهد.
  • دومی کل مقاله را به همراه نموداری که با استفاده از CustomPaint ایجاد شده است، نمایش می‌دهد.

.

9c02f8b62c1faa3a.pngd97d44051304cae4.png

آنچه یاد خواهید گرفت

  • نحوه ایجاد ویجت‌های صفحه اصلی در iOS و اندروید
  • نحوه استفاده از پکیج home_widget برای اشتراک‌گذاری داده‌ها بین ویجت صفحه اصلی و برنامه Flutter شما.
  • چگونه میزان کدی را که باید دوباره بنویسید کاهش دهید.
  • نحوه به‌روزرسانی ویجت صفحه اصلی از برنامه Flutter.

۲. محیط توسعه خود را تنظیم کنید

برای هر دو پلتفرم، به SDK فلاتر و یک IDE نیاز دارید. می‌توانید از IDE مورد نظر خود برای کار با فلاتر استفاده کنید. این می‌تواند Visual Studio Code با Dart Code و افزونه‌های Flutter یا Android Studio یا IntelliJ با افزونه‌های Flutter و Dart نصب شده باشد.

برای ایجاد ویجت صفحه اصلی iOS:

  • شما می‌توانید این codelab را روی یک دستگاه iOS فیزیکی یا شبیه‌ساز iOS اجرا کنید.
  • شما باید یک سیستم macOS را با Xcode IDE پیکربندی کنید. این کار کامپایلر مورد نیاز برای ساخت نسخه iOS برنامه شما را نصب می‌کند.

برای ایجاد ویجت صفحه اصلی اندروید:

  • شما می‌توانید این codelab را روی یک دستگاه اندروید فیزیکی یا شبیه‌ساز اندروید اجرا کنید.
  • شما باید سیستم توسعه خود را با اندروید استودیو پیکربندی کنید. این کار کامپایلر مورد نیاز برای ساخت نسخه اندروید برنامه شما را نصب می‌کند.

کد شروع را دریافت کنید

نسخه اولیه پروژه خود را از 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 پروژه اضافه شدند. برای بازیابی وابستگی‌های پروژه، دستور زیر را اجرا کنید:

$ flutter pub get

۳. یک ویجت پایه به صفحه اصلی اضافه کنید

ابتدا، ویجت صفحه اصلی را با استفاده از ابزار بومی پلتفرم اضافه کنید.

ایجاد یک ویجت ساده برای صفحه اصلی iOS

افزودن یک افزونه برنامه به برنامه Flutter iOS شما مشابه افزودن یک افزونه برنامه به یک برنامه SwiftUI یا UIKit است:

  1. open ios/Runner.xcworkspace در یک پنجره ترمینال از دایرکتوری پروژه Flutter خود اجرا کنید. روش دیگر، کلیک راست روی پوشه ios از VSCode و انتخاب گزینه Open in Xcode است. این کار فضای کاری پیش‌فرض Xcode را در پروژه Flutter شما باز می‌کند.
  2. از منو، مسیر File → New → Target را انتخاب کنید. این کار یک target جدید به پروژه اضافه می‌کند.
  3. فهرستی از قالب‌ها نمایش داده می‌شود. افزونه ویجت را انتخاب کنید.
  4. عبارت "NewsWidgets" را در کادر نام محصول برای این ویجت تایپ کنید. هر دو کادر انتخاب "Include Live Activity" و "Include Configuration Intent" را پاک کنید.

کد نمونه را بررسی کنید

وقتی یک هدف جدید اضافه می‌کنید، Xcode بر اساس الگویی که انتخاب کرده‌اید، کد نمونه تولید می‌کند. برای اطلاعات بیشتر در مورد کد تولید شده و WidgetKit، به مستندات افزونه برنامه اپل مراجعه کنید.

ویجت نمونه خود را اشکال‌زدایی و آزمایش کنید

  1. ابتدا، پیکربندی برنامه Flutter خود را به‌روزرسانی کنید. این کار را باید زمانی انجام دهید که بسته‌های جدیدی را به برنامه Flutter خود اضافه می‌کنید و قصد دارید یک هدف را در پروژه از Xcode اجرا کنید. برای به‌روزرسانی پیکربندی برنامه خود، دستور زیر را در دایرکتوری برنامه Flutter خود اجرا کنید:
$ flutter build ios --config-only
  1. برای نمایش لیستی از اهداف، روی Runner کلیک کنید. هدف ویجتی که اخیراً ایجاد کرده‌اید، NewsWidgets، را انتخاب کنید و روی Run کلیک کنید. هنگام تغییر کد ویجت iOS، هدف ویجت را از Xcode اجرا کنید.

bbb519df1782881d.png

  1. شبیه‌ساز یا صفحه دستگاه باید یک ویجت صفحه اصلی (Home Screen) را نمایش دهد. اگر آن را نمی‌بینید، می‌توانید آن را به صفحه اضافه کنید. روی صفحه اصلی کلیک کنید و نگه دارید، سپس روی + در گوشه بالا سمت چپ کلیک کنید.

18eff1cae152014d.png

  1. نام برنامه را جستجو کنید. برای این codelab، عبارت "Homescreen Widgets" را جستجو کنید.

a0c00df87615493e.png

  1. وقتی ویجت صفحه اصلی را اضافه کردید، باید متن ساده‌ای که زمان را نشان می‌دهد، نمایش داده شود.

ایجاد یک ویجت پایه اندروید

  1. برای افزودن ویجت صفحه اصلی در اندروید، فایل ساخت پروژه را در اندروید استودیو باز کنید. می‌توانید این فایل را در android/build.gradle پیدا کنید. روش دیگر، کلیک راست روی پوشه اندروید از VSCode و انتخاب گزینه Open in Android Studio است.
  2. پس از ساخت پروژه، پوشه app را در گوشه بالا سمت چپ پیدا کنید. ویجت صفحه اصلی جدید خود را به این پوشه اضافه کنید. روی پوشه کلیک راست کنید، New -> Widget -> App Widget را انتخاب کنید.

f19d8b7f95ab884e.png

  1. اندروید استودیو یک فرم جدید نمایش می‌دهد. اطلاعات اولیه در مورد ویجت صفحه اصلی خود از جمله نام کلاس، محل قرارگیری، اندازه و زبان منبع آن را اضافه کنید.

برای این codelab، مقادیر زیر را تنظیم کنید:

  • کادر نام کلاس به NewsWidget
  • منوی کشویی حداقل عرض (سلول‌ها) به ۳
  • منوی کشویی حداقل ارتفاع (سلول‌ها) به ۳

کد نمونه را بررسی کنید

وقتی فرم را ارسال می‌کنید، اندروید استودیو چندین فایل ایجاد و به‌روزرسانی می‌کند. تغییرات مربوط به این codelab در جدول زیر فهرست شده‌اند.

اکشن

فایل هدف

تغییر

به‌روزرسانی

AndroidManifest.xml

یک گیرنده جدید اضافه می‌کند که NewsWidget را ثبت می‌کند.

ایجاد کردن

res/layout/news_widget.xml

رابط کاربری ویجت صفحه اصلی را تعریف می‌کند.

ایجاد کردن

res/xml/news_widget_info.xml

پیکربندی ویجت صفحه اصلی شما را تعریف می‌کند. می‌توانید ابعاد یا نام ویجت خود را در این فایل تنظیم کنید.

ایجاد کردن

java/com/example/homescreen_widgets/NewsWidget.kt

شامل کد کاتلین شما برای افزودن قابلیت به ویجت صفحه اصلی شما است.

می‌توانید جزئیات بیشتری در مورد این فایل‌ها را در این codelab پیدا کنید.

ویجت نمونه خود را اشکال‌زدایی و آزمایش کنید

حالا، برنامه خود را اجرا کنید و ویجت صفحه اصلی را ببینید. پس از ساخت برنامه، به صفحه انتخاب برنامه در دستگاه اندروید خود بروید و روی آیکون این پروژه فلاتر لمس طولانی کنید. از منوی باز شده، ویجت‌ها را انتخاب کنید.

dff7c9f9f85ef1c7.png

دستگاه یا شبیه‌ساز اندروید، ویجت پیش‌فرض صفحه اصلی شما را برای اندروید نمایش می‌دهد.

۴. ارسال داده‌ها از برنامه Flutter به ویجت صفحه اصلی

شما می‌توانید ویجت صفحه اصلی که ایجاد کرده‌اید را سفارشی کنید. ویجت صفحه اصلی را به‌روزرسانی کنید تا عنوان و خلاصه‌ای از یک مقاله خبری را نمایش دهد. تصویر زیر نمونه‌ای از ویجت صفحه اصلی را نشان می‌دهد که عنوان و خلاصه‌ای را نمایش می‌دهد.

acb90343a3e51b6d.png

برای انتقال داده‌ها بین برنامه و ویجت صفحه اصلی، باید کد Dart و native بنویسید. این بخش این فرآیند را به سه بخش تقسیم می‌کند:

  1. نوشتن کد دارت در برنامه Flutter که هم اندروید و هم iOS بتوانند از آن استفاده کنند
  2. افزودن قابلیت‌های بومی iOS
  3. اضافه کردن قابلیت‌های بومی اندروید

استفاده از گروه‌های اپلیکیشن iOS

برای اشتراک‌گذاری داده‌ها بین یک برنامه‌ی مادر iOS و یک افزونه‌ی ویجت، هر دو هدف باید به یک گروه برنامه تعلق داشته باشند. برای کسب اطلاعات بیشتر در مورد گروه‌های برنامه، به مستندات گروه برنامه‌ی اپل مراجعه کنید.

شناسه بسته خود را به‌روزرسانی کنید:

در Xcode، به تنظیمات هدف خود بروید. در تب Signing & Capabilities ، بررسی کنید که شناسه تیم و بسته شما تنظیم شده باشد.

گروه App را هم به Runner target و هم به NewsWidgetExtension target در Xcode اضافه کنید:

+ قابلیت -> گروه‌های برنامه را انتخاب کنید و یک گروه برنامه جدید اضافه کنید. این کار را برای هر دو هدف Runner (برنامه والد) و هدف ویجت تکرار کنید.

۱۳۵e1a8c4652dac.png

کد دارت را اضافه کنید

هر دو برنامه iOS و اندروید می‌توانند داده‌ها را با یک برنامه Flutter به چند روش مختلف به اشتراک بگذارند. برای برقراری ارتباط با این برنامه‌ها، از مخزن key/value محلی دستگاه استفاده کنید. iOS این مخزن را UserDefaults و اندروید آن را 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 را در خود نگه می‌دارد. این تابع همچنین به پلتفرم بومی اطلاع می‌دهد که داده‌های جدید برای ویجت‌های صفحه اصلی قابل بازیابی و رندر هستند.

دکمه شناور ActionButton را تغییر دهید

تابع 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 را برای نمایش داده‌های مقاله به‌روزرسانی کنید

برای به‌روزرسانی ویجت صفحه اصلی برای iOS، از Xcode استفاده کنید.

فایل 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 ، مستندات TimelineEntry اپل را بررسی کنید.

ویرایش 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 سه متد مختلف دارد:

  1. متد placeholder زمانی که کاربر برای اولین بار پیش‌نمایش ویجت صفحه اصلی را مشاهده می‌کند، یک ورودی placeholder ایجاد می‌کند.

45a0f64240c12efe.png

  1. متد getSnapshot داده‌ها را از مقادیر پیش‌فرض کاربر می‌خواند و ورودی مربوط به زمان فعلی را تولید می‌کند.
  2. متد getTimeline ورودی‌های جدول زمانی را برمی‌گرداند. این به شما کمک می‌کند زمانی که نقاط زمانی قابل پیش‌بینی برای به‌روزرسانی محتوای خود دارید. این آزمایشگاه کد از تابع getSnapshot برای دریافت وضعیت فعلی استفاده می‌کند. متد .atEnd به ویجت صفحه اصلی می‌گوید که داده‌ها را پس از گذشت زمان فعلی به‌روزرسانی کند.

پیش‌نمایش‌های NewsWidgets_Previews را کامنت کنید

استفاده از پیش‌نمایش‌ها خارج از محدوده‌ی این آزمایشگاه کد است. برای جزئیات بیشتر در مورد پیش‌نمایش ویجت‌های صفحه اصلی SwiftUI، به مستندات اپل در مورد اشکال‌زدایی ویجت‌ها مراجعه کنید.

تمام فایل‌ها را ذخیره کنید و برنامه و ویجت را دوباره اجرا کنید.

اهداف را دوباره اجرا کنید تا تأیید کنید که برنامه و ویجت صفحه اصلی کار می‌کنند.

  1. طرحواره برنامه را در Xcode انتخاب کنید تا هدف برنامه اجرا شود.
  2. طرحواره افزونه را در Xcode انتخاب کنید تا هدف افزونه اجرا شود.
  3. به صفحه مقاله در برنامه بروید.
  4. برای به‌روزرسانی عنوان، روی دکمه کلیک کنید. ویجت صفحه اصلی نیز باید عنوان را به‌روزرسانی کند.

کد اندروید را به‌روزرسانی کنید

ویجت XML صفحه اصلی را اضافه کنید.

در اندروید استودیو، فایل‌های تولید شده در مرحله قبل را به‌روزرسانی کنید. فایل res/layout/news_widget.xml . این فایل ساختار و طرح‌بندی ویجت صفحه اصلی شما را تعریف می‌کند. در گوشه بالا سمت راست، گزینه Code را انتخاب کنید و محتویات آن فایل را با کد زیر جایگزین کنید:

اندروید/اپ/رز/لایوت/نیوز_ویجت.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 دو نمای متن تعریف می‌کند، یکی برای عنوان مقاله و دیگری برای توضیحات مقاله. این نماهای متن همچنین سبک‌بندی را تعریف می‌کنند. در طول این آزمایشگاه کد به این فایل باز خواهید گشت.

به‌روزرسانی قابلیت ویجت اخبار

فایل کد منبع کاتلین NewsWidget.kt را باز کنید. این فایل شامل یک کلاس تولید شده به نام NewsWidget است که از کلاس AppWidgetProvider ارث‌بری می‌کند.

کلاس NewsWidget شامل سه متد از کلاس بالادست خود است. شما متد onUpdate تغییر خواهید داد. اندروید این متد را برای ویجت‌ها در فواصل زمانی ثابت فراخوانی می‌کند.

محتویات فایل 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 فراخوانی می‌شود، اندروید جدیدترین مقادیر را با استفاده از متد the widgetData.getString() از حافظه محلی دریافت می‌کند و سپس setTextViewText برای تغییر متن نمایش داده شده در ویجت صفحه اصلی فراخوانی می‌کند.

به‌روزرسانی‌ها را آزمایش کنید

برنامه را آزمایش کنید تا مطمئن شوید ویجت‌های صفحه اصلی شما با داده‌های جدید به‌روزرسانی می‌شوند. برای به‌روزرسانی داده‌ها، FloatingActionButton به‌روزرسانی صفحه اصلی» در صفحات مقاله استفاده کنید. ویجت صفحه اصلی شما باید با عنوان مقاله به‌روزرسانی شود.

5ce1c9914b43ad79.png

۵. استفاده از فونت‌های سفارشی برنامه Flutter در ویجت صفحه اصلی iOS شما

تا اینجا، شما ویجت صفحه اصلی را برای خواندن داده‌هایی که برنامه Flutter ارائه می‌دهد، پیکربندی کرده‌اید. برنامه Flutter شامل یک فونت سفارشی است که ممکن است بخواهید در ویجت صفحه اصلی از آن استفاده کنید. می‌توانید از فونت سفارشی در ویجت صفحه اصلی iOS خود استفاده کنید. استفاده از فونت‌های سفارشی در ویجت‌های صفحه اصلی در اندروید در دسترس نیست.

کد iOS را به‌روزرسانی کنید

فلاتر دارایی‌های خود را در mainBundle برنامه‌های iOS ذخیره می‌کند. شما می‌توانید از طریق کد ویجت صفحه اصلی خود به دارایی‌های موجود در این bundle دسترسی داشته باشید.

در ساختار NewsWidgetsEntryView در فایل NewsWidgets.swift خود، تغییرات زیر را اعمال کنید

یک تابع کمکی برای دریافت مسیر دایرکتوری asset در فلاتر ایجاد کنید:

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

وقتی ویجت صفحه اصلی خود را اجرا می‌کنید، اکنون از فونت سفارشی برای عنوان استفاده می‌کند، همانطور که در تصویر زیر نشان داده شده است:

۹۳f۸b۹d۷۶۷aacfb۲.png

۶. رندر کردن ویجت‌های فلاتر به عنوان تصویر

در این بخش، نموداری از برنامه Flutter خود را به عنوان یک ویجت صفحه اصلی نمایش خواهید داد.

این ویجت چالش بزرگ‌تری نسبت به متنی که روی صفحه اصلی نمایش داده‌اید، ایجاد می‌کند. نمایش نمودار فلاتر به صورت تصویر بسیار آسان‌تر از تلاش برای بازسازی آن با استفاده از اجزای رابط کاربری بومی است.

ویجت صفحه اصلی خود را طوری کدنویسی کنید که نمودار فلاتر شما را به صورت یک فایل PNG رندر کند. ویجت صفحه اصلی شما می‌تواند آن تصویر را نمایش دهد.

کد دارت را بنویسید

در سمت Dart، متد renderFlutterWidget از پکیج home_widget اضافه کنید. این متد یک ویجت، یک نام فایل و یک کلید می‌گیرد. تصویری از ویجت Flutter را برمی‌گرداند و آن را در یک کانتینر مشترک ذخیره می‌کند. نام تصویر را در کد خود وارد کنید و مطمئن شوید که ویجت Home Screen می‌تواند به کانتینر دسترسی داشته باشد. key مسیر کامل فایل را به صورت یک رشته در حافظه محلی دستگاه ذخیره می‌کند. این به ویجت Home Screen اجازه می‌دهد در صورت تغییر نام فایل در کد Dart، آن را پیدا کند.

برای این کد، کلاس LineChart در فایل lib/article_screen.dart نمودار را نمایش می‌دهد. متد build آن یک 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!),
        ],
      ),
    );
  }
}

این مثال سه تغییر در کلاس _ArticleScreenState ایجاد می‌کند.

یک کلید عمومی (GlobalKey) ایجاد می‌کند

GlobalKey زمینه‌ی ویجت خاص را دریافت می‌کند که برای دریافت اندازه‌ی آن ویجت مورد نیاز است.

lib/article_screen.dart

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

مسیر تصویر را اضافه می‌کند

ویژگی imagePath موقعیت مکانی تصویر را که ویجت Flutter در آن رندر می‌شود، ذخیره می‌کند.

lib/article_screen.dart

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

کلید را به ویجت برای رندر اضافه می‌کند

_globalKey شامل ویجت Flutter است که به تصویر رندر می‌شود. در این مورد، ویجت Flutter همان Center است که LineChart در خود جای داده است.

lib/article_screen.dart

class _ArticleScreenState extends State<ArticleScreen> {
   ...
   Center(
      // New: Add this key
 key: _globalKey,
 child: const LineChart(),
   ),
   ...
}
  1. ویجت را به عنوان تصویر ذخیره می‌کند

متد renderFlutterWidget زمانی فراخوانی می‌شود که کاربر روی floatingActionButton کلیک کند. این متد فایل PNG حاصل را به عنوان "screenshot" در دایرکتوری کانتینر مشترک ذخیره می‌کند. این متد همچنین مسیر کامل تصویر را به عنوان کلید نام فایل در حافظه دستگاه ذخیره می‌کند.

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 اندازه ویجت صفحه اصلی (Home Screen) را در دستگاه کاربر نگه می‌دارد. اندازه ویجت صفحه اصلی از context می‌آید.

ios/NewsWidgets/NewsWidgets.swift

struct NewsArticleEntry: TimelineEntry {
   ...

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

تابع placeholder را به‌روزرسانی کنید

یک filename و displaySize به عنوان placeholder قرار دهید.

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 که تصویر را از یک مسیر نمایش می‌دهد

نمای ChartImage از محتویات فایل تولید شده در سمت Dart، تصویری ایجاد می‌کند. در اینجا، اندازه را روی ۵۰٪ از قاب تنظیم می‌کنید.

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

کد اندروید را به‌روزرسانی کنید

کد اندروید همانند کد iOS عمل می‌کند.

  1. فایل android/app/res/layout/news_widget.xml را باز کنید. این فایل شامل عناصر رابط کاربری ویجت صفحه اصلی شما است. محتویات آن را با کد زیر جایگزین کنید:

اندروید/اپ/رز/لایوت/نیوز_ویجت.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)
        }
    }
}

این کد دارت، یک اسکرین‌شات را با کلید filename در حافظه محلی ذخیره می‌کند. همچنین مسیر کامل تصویر را دریافت کرده و یک شیء File از آن ایجاد می‌کند. اگر تصویر وجود داشته باشد، کد دارت تصویر موجود در ویجت صفحه اصلی را با تصویر جدید جایگزین می‌کند.

  1. برنامه خود را مجدداً بارگذاری کنید و به صفحه مقاله بروید. به‌روزرسانی صفحه اصلی را فشار دهید. ویجت صفحه اصلی نمودار را نمایش می‌دهد.

۷. مراحل بعدی

تبریک می‌گویم!

تبریک می‌گویم، شما در ایجاد ویجت‌های صفحه اصلی برای برنامه‌های Flutter iOS و اندروید خود موفق شدید!

لینک دادن به محتوا در برنامه Flutter شما

ممکن است بخواهید کاربر را بسته به جایی که کلیک می‌کند، به صفحه خاصی در برنامه خود هدایت کنید. برای مثال، در برنامه خبری از این codelab، ممکن است بخواهید کاربر مقاله خبری مربوط به تیتر نمایش داده شده را ببیند.

این ویژگی خارج از محدوده این آزمایشگاه کد است. می‌توانید نمونه‌هایی از استفاده از جریانی که بسته home_widget برای شناسایی راه‌اندازی برنامه از ویجت‌های صفحه اصلی و ارسال پیام از ویجت صفحه اصلی از طریق URL ارائه می‌دهد را بیابید. برای کسب اطلاعات بیشتر، به مستندات پیوند عمیق در docs.flutter.dev مراجعه کنید.

به‌روزرسانی ویجت در پس‌زمینه

در این آزمایشگاه کد، شما با استفاده از یک دکمه، به‌روزرسانی ویجت صفحه اصلی را فعال کردید. اگرچه این برای آزمایش منطقی است، اما در کد عملیاتی ممکن است بخواهید برنامه شما ویجت صفحه اصلی را در پس‌زمینه به‌روزرسانی کند. می‌توانید از افزونه workmanager برای ایجاد وظایف پس‌زمینه برای به‌روزرسانی منابع مورد نیاز ویجت صفحه اصلی استفاده کنید. برای کسب اطلاعات بیشتر، بخش به‌روزرسانی پس‌زمینه را در بسته home_widget بررسی کنید.

برای iOS، می‌توانید از ویجت صفحه اصلی بخواهید که برای به‌روزرسانی رابط کاربری خود، درخواست شبکه ارسال کند. برای کنترل شرایط یا دفعات آن درخواست، از Timeline استفاده کنید. برای کسب اطلاعات بیشتر در مورد استفاده از Timeline، به مستندات «به‌روز نگه‌داشتن ویجت» اپل مراجعه کنید.

مطالعه بیشتر