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

1. مقدمه

ویجت ها چیست؟

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

f0027e8a7d0237e0.pngb991e79ea72c8b65.png

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

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

819b9fffd700e571.png92d62ccfd17d770d.png

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

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

چیزی که خواهی ساخت

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

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

a36b7ba379151101.png

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

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

.

9c02f8b62c1faa3a.pngd97d44051304cae4.png

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

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

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

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

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

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

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

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

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

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

$ flutter pub get

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

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

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

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

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

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

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

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

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

bbb519df1782881d.png

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

18eff1cae152014d.png

  1. نام برنامه را جستجو کنید. برای این کد لبه، "ویجت های صفحه اصلی" را جستجو کنید

a0c00df87615493e.png

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

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

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

f19d8b7f95ab884e.png

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

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

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

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

هنگامی که فرم را ارسال می کنید، Android Studio چندین فایل ایجاد و به روز می کند. تغییرات مربوط به این کد لبه در جدول زیر فهرست شده است

اقدام

فایل هدف

تغییر دهید

به روز رسانی

AndroidManifest.xml

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

ایجاد کنید

res/layout/news_widget.xml

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

ایجاد کنید

res/xml/news_widget_info.xml

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

ایجاد کنید

java/com/example/homescreen_widgets/NewsWidget.kt

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

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

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

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

dff7c9f9f85ef1c7.png

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

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

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

acb90343a3e51b6d.png

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

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

استفاده از گروه های برنامه iOS

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

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

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

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

+ Capability -> App Groups را انتخاب کنید و یک App Group جدید اضافه کنید. هم برای هدف Runner (برنامه والد) و هم برای هدف ویجت تکرار کنید.

135e1a8c4652dac.png

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

هم برنامه‌های iOS و Android می‌توانند داده‌ها را با یک برنامه 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 را نگه می دارد. این تابع همچنین به پلتفرم بومی اطلاع می‌دهد که داده‌های جدید برای ویجت‌های صفحه اصلی قابل بازیابی و رندر هستند.

floatingActionButton را تغییر دهید

هنگامی که دکمه floatingActionButton فشار داده می شود، تابع updateHeadline را فراخوانی کنید:

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

با این تغییر، زمانی که کاربر دکمه Update Headline را از صفحه مقاله فشار می دهد، جزئیات ویجت صفحه اصلی به روز می شود.

کد 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 موجود کنید. سپس، شناسه گروه برنامه خود را با <گروه برنامه شما> جایگزین کنید:

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 زمانی که کاربر برای اولین بار ویجت صفحه اصلی را پیش‌نمایش می‌کند، یک ورودی مکان‌نما ایجاد می‌کند.

45a0f64240c12efe.png

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

NewsWidgets_Previews را نظر دهید

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

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

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

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

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

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

قابلیت NewsWidget را به روز کنید

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

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

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

5ce1c9914b43ad79.png

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

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

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

Flutter دارایی های خود را در باندل اصلی برنامه های 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 خود به عنوان ویجت صفحه اصلی نمایش می دهید.

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

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

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

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

برای این کد، کلاس 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!),
        ],
      ),
    );
  }
}

این مثال سه تغییر در کلاس _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 ایجاد کنید که تصویر را از یک مسیر نمایش دهد

نمای ChartImage یک تصویر از محتویات فایل تولید شده در سمت دارت ایجاد می کند. در اینجا، اندازه را روی 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

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

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

  1. فایل android/app/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" />
   
   <!--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 از آن ایجاد می کند. اگر تصویر وجود داشته باشد، کد Dart تصویر جدید را در ویجت صفحه اصلی جایگزین می کند.

  1. برنامه خود را دوباره بارگیری کنید و به صفحه مقاله بروید. Update Homescreen را فشار دهید. ویجت صفحه اصلی نمودار را نمایش می دهد.

7. مراحل بعدی

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

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

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

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

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

در حال به روز رسانی ویجت خود در پس زمینه

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

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

در ادامه مطلب