پیام ها را در یک برنامه Flutter دریافت کنید

بسته به وضعیت دستگاه، پیام‌های دریافتی به گونه‌ای متفاوت مدیریت می‌شوند. برای درک این سناریوها و نحوه ادغام FCM در برنامه خود، ابتدا مهم است که وضعیت‌های مختلفی را که یک دستگاه می‌تواند در آن قرار گیرد مشخص کنید:

ایالت توضیحات
پیش زمینه وقتی برنامه باز است، در حال مشاهده و در حال استفاده است.
پس زمینه هنگامی که برنامه باز است، اما در پس زمینه (به حداقل می رسد). این معمولاً زمانی اتفاق می‌افتد که کاربر دکمه «خانه» را روی دستگاه فشار داده باشد، با استفاده از تعویض‌کننده برنامه به برنامه دیگری تغییر مکان داده باشد، یا برنامه در یک برگه دیگر (وب) باز شود.
خاتمه یافت وقتی دستگاه قفل است یا برنامه اجرا نمی شود.

قبل از اینکه برنامه بتواند محموله های پیام را از طریق FCM دریافت کند، چند پیش شرط وجود دارد:

  • برنامه باید حداقل یک بار باز شده باشد (برای ثبت نام در FCM).
  • در iOS، اگر کاربر برنامه را از سوئیچر برنامه دور کند، باید به صورت دستی باز شود تا پیام‌های پس‌زمینه دوباره شروع به کار کنند.
  • در اندروید، اگر کاربر برنامه را به اجبار از تنظیمات دستگاه خارج کند، باید به صورت دستی باز شود تا پیام ها شروع به کار کنند.
  • در وب، باید یک توکن (با استفاده از getToken() ) همراه با گواهی فشار وب خود درخواست کرده باشید.

درخواست مجوز برای دریافت پیام

در iOS، macOS، وب و اندروید 13 (یا جدیدتر)، قبل از دریافت بارهای FCM در دستگاه شما، ابتدا باید از کاربر اجازه بگیرید.

بسته firebase_messaging یک API ساده برای درخواست مجوز از طریق متد requestPermission فراهم می کند. این API تعدادی آرگومان نام‌گذاری شده را می‌پذیرد که نوع مجوزهایی را که می‌خواهید درخواست کنید، تعریف می‌کنند، مانند اینکه آیا پیام‌های حاوی بارهای اعلان می‌توانند صدا را تحریک کنند یا پیام‌ها را از طریق Siri بخوانند. به طور پیش فرض، متد مجوزهای پیش فرض معقولی را درخواست می کند. API مرجع مستندات کاملی را در مورد اینکه هر مجوز برای چه چیزی است ارائه می دهد.

برای شروع، روش را از برنامه خود فراخوانی کنید (در iOS یک مدال بومی نمایش داده می شود، در وب جریان API بومی مرورگر فعال می شود):

FirebaseMessaging messaging = FirebaseMessaging.instance;

NotificationSettings settings = await messaging.requestPermission(
  alert: true,
  announcement: false,
  badge: true,
  carPlay: false,
  criticalAlert: false,
  provisional: false,
  sound: true,
);

print('User granted permission: ${settings.authorizationStatus}');

ویژگی authorizationStatus شی NotificationSettings که از درخواست بازگردانده شده است می تواند برای تعیین تصمیم کلی کاربر استفاده شود:

  • authorized : کاربر اجازه داده است.
  • denied : کاربر اجازه رد کرد.
  • notDetermined : کاربر هنوز انتخاب نکرده است که مجوز بدهد یا خیر.
  • provisional : کاربر مجوز موقت داده است

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

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

مدیریت پیام

بر اساس وضعیت فعلی برنامه شما، بارهای دریافتی از انواع پیام های مختلف به پیاده سازی های متفاوتی برای رسیدگی به آنها نیاز دارند:

پیام های پیش زمینه

برای مدیریت پیام ها در حالی که برنامه شما در پیش زمینه است، به جریان onMessage گوش دهید.

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Got a message whilst in the foreground!');
  print('Message data: ${message.data}');

  if (message.notification != null) {
    print('Message also contained a notification: ${message.notification}');
  }
});

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

پیش زمینه و پیام های اعلان

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

  • در اندروید، باید یک کانال اعلان با «اولویت بالا» ایجاد کنید.
  • در iOS، می توانید گزینه های ارائه برنامه را به روز کنید.

پیام های پس زمینه

فرآیند مدیریت پیام های پس زمینه در پلتفرم های بومی (اندروید و اپل) و مبتنی بر وب متفاوت است.

پلتفرم های اپل و اندروید

با ثبت یک کنترل کننده onBackgroundMessage پیام های پس زمینه را مدیریت کنید. هنگامی که پیام‌ها دریافت می‌شوند، یک ایزوله ایجاد می‌شود (فقط اندروید، iOS/macOS نیازی به جداسازی جداگانه ندارد) که به شما امکان می‌دهد پیام‌ها را حتی زمانی که برنامه شما اجرا نمی‌شود مدیریت کنید.

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

  1. نباید یک تابع ناشناس باشد.
  2. این باید یک تابع سطح بالا باشد (مثلاً یک متد کلاس که نیاز به مقداردهی اولیه دارد).
  3. هنگام استفاده از Flutter نسخه 3.3.0 یا بالاتر، کنترل کننده پیام باید با @pragma('vm:entry-point') درست بالای اعلان تابع حاشیه نویسی شود (در غیر این صورت ممکن است هنگام تکان دادن درخت برای حالت انتشار حذف شود).
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp();

  print("Handling a background message: ${message.messageId}");
}

void main() {
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

از آنجایی که کنترل کننده در ایزوله خود خارج از زمینه برنامه های کاربردی شما اجرا می شود، امکان به روز رسانی وضعیت برنامه یا اجرای منطق تأثیرگذار UI وجود ندارد. با این حال، می‌توانید منطقی مانند درخواست‌های HTTP، عملیات IO (مثلاً به‌روزرسانی فضای ذخیره‌سازی محلی)، برقراری ارتباط با سایر افزونه‌ها و غیره را انجام دهید.

همچنین توصیه می شود در اسرع وقت منطق خود را تکمیل کنید. انجام کارهای طولانی و فشرده بر عملکرد دستگاه تأثیر می گذارد و ممکن است باعث شود که سیستم عامل فرآیند را خاتمه دهد. اگر کارها بیش از 30 ثانیه اجرا شوند، دستگاه ممکن است به طور خودکار فرآیند را از بین ببرد.

وب

در وب، یک JavaScript Service Worker بنویسید که در پس‌زمینه اجرا می‌شود. از سرویس‌کار برای مدیریت پیام‌های پس‌زمینه استفاده کنید.

برای شروع، یک فایل جدید در فهرست web خود ایجاد کنید و آن را firebase-messaging-sw.js نامید:

// Please see this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js");

firebase.initializeApp({
  apiKey: "...",
  authDomain: "...",
  databaseURL: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
});

const messaging = firebase.messaging();

// Optional:
messaging.onBackgroundMessage((message) => {
  console.log("onBackgroundMessage", message);
});

فایل باید هر دو SDK برنامه و پیام را وارد کند، Firebase را مقداردهی اولیه کند و متغیر messaging را در معرض نمایش قرار دهد.

بعد، کارگر باید ثبت نام کند. در فایل index.html ، با تغییر تگ <script> که Flutter را بوت استرپ می کند، کارگر را ثبت کنید:

<script src="flutter_bootstrap.js" async>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
      navigator.serviceWorker.register('firebase-messaging-sw.js', {
        scope: '/firebase-cloud-messaging-push-scope',
      });
    });
  }
</script>

اگر هنوز از سیستم قالب قدیمی استفاده می کنید، می توانید با تغییر تگ <script> که Flutter را بوت استرپ می کند، کارگر را به صورت زیر ثبت کنید:

<html>
<body>
  <script>
      var serviceWorkerVersion = null;
      var scriptLoaded = false;
      function loadMainDartJs() {
        if (scriptLoaded) {
          return;
        }
        scriptLoaded = true;
        var scriptTag = document.createElement('script');
        scriptTag.src = 'main.dart.js';
        scriptTag.type = 'application/javascript';
        document.body.append(scriptTag);
      }

      if ('serviceWorker' in navigator) {
        // Service workers are supported. Use them.
        window.addEventListener('load', function () {
          // Register Firebase Messaging service worker.
          navigator.serviceWorker.register('firebase-messaging-sw.js', {
            scope: '/firebase-cloud-messaging-push-scope',
          });

          // Wait for registration to finish before dropping the <script> tag.
          // Otherwise, the browser will load the script multiple times,
          // potentially different versions.
          var serviceWorkerUrl =
            'flutter_service_worker.js?v=' + serviceWorkerVersion;

          navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
            function waitForActivation(serviceWorker) {
              serviceWorker.addEventListener('statechange', () => {
                if (serviceWorker.state == 'activated') {
                  console.log('Installed new service worker.');
                  loadMainDartJs();
                }
              });
            }
            if (!reg.active && (reg.installing || reg.waiting)) {
              // No active web worker and we have installed or are installing
              // one for the first time. Simply wait for it to activate.
              waitForActivation(reg.installing ?? reg.waiting);
            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
              // When the app updates the serviceWorkerVersion changes, so we
              // need to ask the service worker to update.
              console.log('New service worker available.');
              reg.update();
              waitForActivation(reg.installing);
            } else {
              // Existing service worker is still good.
              console.log('Loading app from service worker.');
              loadMainDartJs();
            }
          });

          // If service worker doesn't succeed in a reasonable amount of time,
          // fallback to plaint <script> tag.
          setTimeout(() => {
            if (!scriptLoaded) {
              console.warn(
                'Failed to load app from service worker. Falling back to plain <script> tag.'
              );
              loadMainDartJs();
            }
          }, 4000);
        });
      } else {
        // Service workers not supported. Just drop the <script> tag.
        loadMainDartJs();
      }
  </script>
</body>

بعد برنامه Flutter خود را مجددا راه اندازی کنید. کارگر ثبت نام می شود و هرگونه پیام پس زمینه از طریق این فایل بررسی می شود.

مدیریت تعامل

از آنجایی که اعلان‌ها یک نشانه قابل مشاهده هستند، معمولاً کاربران با آنها تعامل دارند (با فشار دادن). رفتار پیش فرض در اندروید و iOS باز کردن برنامه است. اگر برنامه خاتمه یابد، شروع خواهد شد. اگر در پس‌زمینه باشد، در پیش‌زمینه قرار می‌گیرد.

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

بسته firebase-messaging دو راه برای مدیریت این تعامل ارائه می‌دهد:

  • getInitialMessage() : اگر برنامه از حالت پایان یافته باز شود، Future حاوی RemoteMessage برگردانده می شود. پس از مصرف، RemoteMessage حذف خواهد شد.
  • onMessageOpenedApp : Stream که وقتی برنامه از حالت پس‌زمینه باز می‌شود، یک RemoteMessage ارسال می‌کند.

توصیه می شود که هر دو سناریو برای اطمینان از یک UX صاف برای کاربران خود بررسی شوند. مثال کد زیر چگونگی دستیابی به این امر را شرح می دهد:

class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  // It is assumed that all messages contain a data field with the key 'type'
  Future<void> setupInteractedMessage() async {
    // Get any messages which caused the application to open from
    // a terminated state.
    RemoteMessage? initialMessage =
        await FirebaseMessaging.instance.getInitialMessage();

    // If the message also contains a data property with a "type" of "chat",
    // navigate to a chat screen
    if (initialMessage != null) {
      _handleMessage(initialMessage);
    }

    // Also handle any interaction when the app is in the background via a
    // Stream listener
    FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
  }

  void _handleMessage(RemoteMessage message) {
    if (message.data['type'] == 'chat') {
      Navigator.pushNamed(context, '/chat',
        arguments: ChatArguments(message),
      );
    }
  }

  @override
  void initState() {
    super.initState();

    // Run code required to handle interacted messages in an async function
    // as initState() must not be async
    setupInteractedMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Text("...");
  }
}

نحوه مدیریت تعامل به تنظیمات برنامه شما بستگی دارد. مثال بالا یک تصویر اساسی را با استفاده از StatefulWidget نشان می دهد.

بومی سازی پیام ها

شما می توانید رشته های محلی را به دو روش مختلف ارسال کنید:

  • زبان دلخواه هر یک از کاربران خود را در سرور خود ذخیره کنید و اعلان های سفارشی برای هر زبان ارسال کنید
  • رشته های محلی شده را در برنامه خود جاسازی کنید و از تنظیمات محلی سیستم عامل استفاده کنید

در اینجا نحوه استفاده از روش دوم آورده شده است:

اندروید

  1. پیام های زبان پیش فرض خود را در resources/values/strings.xml مشخص کنید:

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. پیام های ترجمه شده را در فهرست راهنمای values- language مشخص کنید. به عنوان مثال، پیام های فرانسوی را در resources/values-fr/strings.xml مشخص کنید:

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. در بار سرور، به جای استفاده از کلیدهای title ، message و body ، از title_loc_key و body_loc_key برای پیام محلی خود استفاده کنید و آنها را روی ویژگی name پیامی که می خواهید نمایش دهید تنظیم کنید.

    محموله پیام به این صورت خواهد بود:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      }
    }
    

iOS

  1. پیام های زبان پیش فرض خود را در Base.lproj/Localizable.strings مشخص کنید:

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. پیام های ترجمه شده را در فهرست راهنمای language .lproj مشخص کنید. به عنوان مثال، پیام های فرانسوی را در fr.lproj/Localizable.strings مشخص کنید:

    "NOTIFICATION_TITLE" = "Bonjour le monde";
    "NOTIFICATION_MESSAGE" = "C'est un message";
    

    محموله پیام به این صورت خواهد بود:

    {
      "data": {
        "title_loc_key": "NOTIFICATION_TITLE",
        "body_loc_key": "NOTIFICATION_MESSAGE"
      }
    }
    

فعال کردن صادرات داده های تحویل پیام

می توانید داده های پیام خود را برای تجزیه و تحلیل بیشتر به BigQuery صادر کنید. BigQuery به شما امکان می‌دهد داده‌ها را با استفاده از BigQuery SQL تجزیه و تحلیل کنید، آن‌ها را به ارائه‌دهنده ابری دیگر صادر کنید یا از داده‌ها برای مدل‌های ML سفارشی خود استفاده کنید. صادرات به BigQuery شامل تمام داده‌های موجود برای پیام‌ها، صرف‌نظر از نوع پیام یا ارسال پیام از طریق API یا سازنده Notifications است.

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

اندروید

می توانید از کد زیر استفاده کنید:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

برای iOS، باید AppDelegate.m را با محتوای زیر تغییر دهید.

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  [[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}

@end

وب

برای وب، برای استفاده از نسخه v9 SDK باید سرویس کار خود را تغییر دهید. نسخه v9 باید باندل شود، بنابراین برای مثال باید از باندلری مانند esbuild استفاده کنید تا سرویس‌کار را به کار بیاورید. برای مشاهده نحوه دستیابی به این برنامه به مثال برنامه مراجعه کنید.

هنگامی که به v9 SDK مهاجرت کردید، می توانید از کد زیر استفاده کنید:

import {
  experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
  getMessaging,
} from 'firebase/messaging/sw';
...

const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);

فراموش نکنید که yarn build اجرا کنید تا نسخه جدید سرویس کارگر خود را به پوشه web صادر کنید.

نمایش تصاویر در اعلان ها در iOS

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

اگر از احراز هویت تلفن Firebase استفاده می کنید، باید غلاف Firebase Auth را به Podfile خود اضافه کنید.

مرحله 1 - یک برنامه افزودنی سرویس اعلان اضافه کنید

  1. در Xcode، روی File > New > Target کلیک کنید...
  2. یک مدال فهرستی از اهداف احتمالی را ارائه می دهد. به پایین پیمایش کنید یا از فیلتر برای انتخاب برنامه افزودنی سرویس اطلاع رسانی استفاده کنید. روی Next کلیک کنید.
  3. نام محصول را اضافه کنید (برای دنبال کردن این آموزش از "ImageNotification" استفاده کنید)، زبان را روی Objective-C تنظیم کنید و روی Finish کلیک کنید.
  4. با کلیک روی فعال کردن، طرح را فعال کنید.

مرحله 2 - هدف را به Podfile اضافه کنید

با افزودن آن در Podfile، اطمینان حاصل کنید که برنامه افزودنی جدید شما به غلاف Firebase/Messaging دسترسی دارد:

  1. از Navigator، Podfile را باز کنید: Pods > Podfile

  2. به پایین فایل بروید و اضافه کنید:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. پادهای خود را با استفاده pod install از فهرست ios یا macos نصب یا به روز کنید.

مرحله 3 - از افزونه کمک کننده استفاده کنید

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

  1. از ناوبر، پسوند ImageNotification خود را انتخاب کنید

  2. فایل NotificationService.m را باز کنید.

  3. در بالای فایل، FirebaseMessaging.h درست بعد از NotificationService.h مطابق شکل زیر وارد کنید.

    محتوای NotificationService.m را با موارد زیر جایگزین کنید:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication
    #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication
    
    @interface NotificationService ()
    
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
    
    @end
    
    @implementation NotificationService
    
    /* Uncomment this if you are using Firebase Auth
    - (BOOL)application:(UIApplication *)app
                openURL:(NSURL *)url
                options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
      if ([[FIRAuth auth] canHandleURL:url]) {
        return YES;
      }
      return NO;
    }
    
    - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
      for (UIOpenURLContext *urlContext in URLContexts) {
        [FIRAuth.auth canHandleURL:urlContext.URL];
      }
    }
    */
    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
    
        // Modify the notification content here...
        [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler];
    }
    
    - (void)serviceExtensionTimeWillExpire {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        self.contentHandler(self.bestAttemptContent);
    }
    
    @end
    

مرحله 4 - تصویر را به محموله اضافه کنید

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