تلقّي الرسائل في تطبيق Flutter

بناءً على حالة الجهاز، يتم التعامل مع الرسائل الواردة بشكل مختلف. لفهم هذه السيناريوهات وكيفية دمج ميزة "المراسلة عبر السحابة الإلكترونية من Firebase" في تطبيقك، يجب أولاً تحديد الحالات المختلفة التي قد يكون فيها الجهاز:

ولاية الوصف
الواجهة عندما يكون التطبيق مفتوحًا ويكون معروضًا وقيد الاستخدام
الخلفية عندما يكون التطبيق مفتوحًا ولكن في الخلفية (مصغر). يحدث هذا عادةً عندما يضغط المستخدم على زر "الصفحة الرئيسية" على الجهاز، أو انتقل إلى تطبيق آخر باستخدام مبدل التطبيقات، أو عندما يفتح التطبيق في علامة تبويب مختلفة (الويب).
تم إنهاؤها عند قفل الجهاز أو عندما لا يكون التطبيق قيد التشغيل.

هناك بعض الشروط المسبقة التي يجب استيفاؤها قبل أن يتلقى التطبيق حمولات الرسائل عبر خدمة "المراسلة عبر السحابة الإلكترونية من Firebase":

  • يجب أن يكون التطبيق قد فتح مرة واحدة على الأقل (للسماح بالتسجيل في خدمة "المراسلة عبر السحابة الإلكترونية من Firebase").
  • على نظام التشغيل iOS، إذا أبعد المستخدم التطبيق عن مبدِّل التطبيقات، يجب إعادة فتحه يدويًا لتبدأ الرسائل في الخلفية في العمل مجددًا.
  • على نظام التشغيل Android، إذا فرض المستخدم إنهاء التطبيق من إعدادات الجهاز، يجب إعادة فتحه يدويًا حتى تبدأ الرسائل في العمل.
  • على الويب، يجب أن تكون قد طلبت رمزًا مميزًا (باستخدام getToken()) مع شهادة Web Push.

طلب إذن لتلقّي الرسائل

على أجهزة iOS وmacOS والويب وAndroid 13 (أو الإصدارات الأحدث)، قبل أن يتم استلام حمولات "المراسلة عبر السحابة الإلكترونية من Firebase" على جهازك، عليك أولاً طلب إذن المستخدم.

توفّر حزمة firebase_messaging واجهة برمجة تطبيقات بسيطة لطلب الإذن عبر طريقة requestPermission. تقبل واجهة برمجة التطبيقات هذه عددًا من الوسيطات المُسمّاة التي تحدّد نوع الأذونات التي ترغب في طلبها، كأن تعمل الرسائل التي تحتوي على حمولات الإشعارات على تشغيل صوت أو قراءة الرسائل عبر Siri. بشكل افتراضي، تطلب الطريقة أذونات افتراضية معقولة. توفّر واجهة برمجة التطبيقات المرجعية مستندات كاملة حول الغرض من كل إذن.

للبدء، يمكنك استدعاء الطريقة من تطبيقك (على iOS، سيتم عرض نموذج مشروط أصلي على الويب، وسيتم بدء تدفق واجهة برمجة التطبيقات الأصلية للمتصفّح على الويب):

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 ما إذا كان إذن معيّن مفعّلاً أو غير مفعَّل أو غير متاح على الجهاز الحالي.

بعد منح الإذن وفهم الأنواع المختلفة لحالات الجهاز، يمكن أن يبدأ تطبيقك الآن في معالجة حمولات "المراسلة عبر السحابة الإلكترونية من Firebase" الواردة.

معالجة الرسائل

استنادًا إلى الحالة الحالية لتطبيقك، تتطلب الحمولات الواردة من أنواع الرسائل المختلفة عمليات تنفيذ مختلفة لمعالجتها:

الرسائل التي تعمل في المقدّمة

للتعامل مع الرسائل عندما يكون التطبيق في المقدّمة، استمع إلى ساحة مشاركات 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 وسياقه.

رسائل المقدمة ورسائل الإشعار

لن تعرض رسائل الإشعارات التي تصل أثناء تشغيل التطبيق في المقدّمة إشعارًا مرئيًا بشكل تلقائي على كل من Android وiOS. ومع ذلك، من الممكن تجاوز هذا السلوك:

  • على جهاز Android، يجب إنشاء قناة إشعارات ذات "أولوية عالية".
  • في نظام iOS، يمكنك تحديث خيارات العرض التقديمي للتطبيق.

الرسائل في الخلفية

تختلف عملية التعامل مع رسائل الخلفية على الأنظمة الأساسية الأصلية (Android وApple) والويب.

أنظمة Apple الأساسية وAndroid

التعامل مع الرسائل في الخلفية من خلال تسجيل معالج onBackgroundMessage عند استلام الرسائل، يبدأ العزل (نظام التشغيل Android فقط، ولا يتطلّب نظام التشغيل iOS/macOS عزلًا منفصلاً)، ما يسمح لك بمعالجة الرسائل حتى في حال عدم تشغيل التطبيق.

هناك بعض الأشياء التي يجب وضعها في الاعتبار بشأن معالج الرسائل الخلفية:

  1. ويجب ألا تكون دالة مجهولة.
  2. ويجب أن تكون دالة ذات مستوى أعلى (على سبيل المثال، ليست طريقة فئة تتطلب إعدادًا).
  3. عند استخدام الإصدار 3.3.0 من Flutter أو الإصدارات الأحدث، يجب إضافة تعليقات توضيحية إلى معالج الرسالة باستخدام @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());
}

نظرًا لأن المعالج يعمل في معزولته الخاصة خارج سياق التطبيقات، فلا يمكن تعديل حالة التطبيق أو تنفيذ أي منطق يؤثر على واجهة المستخدم. ومع ذلك، يمكنك تنفيذ منطق مثل طلبات HTTP، وتنفيذ عمليات IO (مثل تحديث التخزين المحلي)، والتواصل مع المكونات الإضافية الأخرى وما إلى ذلك.

يُوصى أيضًا بإكمال المنطق في أقرب وقت ممكن. يؤثر تشغيل مهام طويلة ومجهدة على أداء الجهاز وقد يتسبب في إنهاء نظام التشغيل للعملية. في حال تنفيذ المهام لمدة تزيد عن 30 ثانية، قد يؤدي الجهاز إلى إنهاء العملية تلقائيًا.

الويب

اكتب مشغّل خدمات JavaScript يتم تشغيله في الخلفية على الويب. استخدام مشغّل الخدمات للتعامل مع رسائل الخلفية.

للبدء، أنشئ ملفًا جديدًا في دليل web، واستدعيه firebase-messaging-sw.js:

importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.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.

بعد ذلك، يجب أن يكون العامل مسجّلاً. ضمن ملف الإدخال، بعد تحميل ملف main.dart.js، سجِّل العامل الخاص بك:

<html>
<body>
  ...
  <script src="main.dart.js" type="application/javascript"></script>
  <script>
       if ('serviceWorker' in navigator) {
          // Service workers are supported. Use them.
          window.addEventListener('load', function () {
            // ADD THIS LINE
            navigator.serviceWorker.register('/firebase-messaging-sw.js');

            // 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;

            //  ...
          });
      }
  </script>

بعد ذلك، أعِد تشغيل تطبيق Flutter. سيتم تسجيل العامل وسيتم التعامل مع أي رسائل في الخلفية عبر هذا الملف.

التعامل مع التفاعل

نظرًا لأن الإشعارات هي إشارة مرئية، فمن الشائع أن يتفاعل المستخدمون معها (عن طريق الضغط). السلوك التلقائي على كل من Android وiOS هو فتح التطبيق. سيتم بدء تشغيل التطبيق في حال إنهائه، أمّا إذا كان يعمل في الخلفية، فسيتم عرضه إلى المقدّمة.

قد ترغب في التعامل مع تفاعل المستخدم عند فتح التطبيق، بناءً على محتوى الإشعار. على سبيل المثال، إذا تم إرسال رسالة محادثة جديدة عبر إشعار وضغط المستخدم عليها، فقد ترغب في فتح المحادثة المحددة عند فتح التطبيق.

توفِّر حزمة firebase-messaging طريقتَين للتعامل مع هذا التفاعل:

  • getInitialMessage(): إذا تم فتح التطبيق من حالة تم إنهاؤها، سيتم عرض Future يحتوي على RemoteMessage. وستتم إزالة RemoteMessage بعد استهلاكها.
  • onMessageOpenedApp: عنصر Stream الذي ينشر RemoteMessage عند فتح التطبيق في حالة الخلفية.

من المستحسن معالجة كلا السيناريوهَين لضمان تجربة مستخدم سلسة للمستخدمين. يوضح مثال الرمز أدناه كيفية تحقيق ذلك:

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.

أقلمة الرسائل

يمكنك إرسال سلاسل مترجَمة بطريقتين مختلفتين:

  • يمكنك تخزين اللغة المفضّلة لكل مستخدم من المستخدمين في خادمك وإرسال إشعارات مخصّصة لكل لغة.
  • يمكنك تضمين سلاسل مترجَمة في تطبيقك والاستفادة من إعدادات اللغة الأصلية في نظام التشغيل.

إليك كيفية استخدام الطريقة الثانية:

Android

  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 أو تصديرها إلى موفّر آخر لخدمات السحابة الإلكترونية أو استخدام البيانات لنماذج تعلُّم الآلة المخصّصة. تتضمن عملية التصدير إلى BigQuery جميع البيانات المتاحة للرسائل، بغض النظر عن نوع الرسالة أو ما إذا تم إرسالها عبر واجهة برمجة التطبيقات أو منشئ الإشعارات.

لتفعيل عملية التصدير، اتّبِع أولاً الخطوات الموضّحة هنا، ثم اتّبِع التعليمات التالية:

Android

يمكنك استخدام الرمز التالي:

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

الويب

بالنسبة إلى المواقع الإلكترونية، يجب تغيير مشغّل الخدمات لاستخدام الإصدار 9 من حزمة تطوير البرامج (SDK). يجب تجميع الإصدار v9 في حزمة، لذا عليك استخدام أداة تجميع مثل esbuild كي يعمل مشغّل الخدمات. يمكنك الاطّلاع على مثال التطبيق لمعرفة كيفية تحقيق ذلك.

بعد النقل إلى الإصدار 9 من حزمة تطوير البرامج (SDK)، يمكنك استخدام الرمز التالي:

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

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

لا تنسَ تشغيل yarn build لتصدير الإصدار الجديد من مشغّل الخدمات إلى مجلد web.

عرض الصور في الإشعارات على أجهزة iOS

على أجهزة Apple، لكي تعرض إشعارات "المراسلة عبر السحابة الإلكترونية من Firebase" الواردة الصور من حمولة البيانات في خدمة "المراسلة عبر السحابة الإلكترونية من Firebase"، يجب إدراج امتداد خدمة إشعارات إضافي وتهيئة التطبيق لاستخدامه.

إذا كنت تستخدم مصادقة الهاتف من Firebase، يجب إضافة لوحة مصادقة Firebase إلى Podfile.

الخطوة 1: إدراج إضافة خدمة الإشعارات

  1. في Xcode، انقر على ملف > جديد > الهدف...
  2. سيعرِض النموذج قائمة بالأهداف المحتملة. انتقِل للأسفل أو استخدِم الفلتر لاختيار إضافة خدمة الإشعارات. انقر على Next (التالي).
  3. أضِف اسم منتج (استخدِم "ImageNotification" لمتابعة هذا البرنامج التعليمي)، واضبط اللغة على Objective-C، ثم انقر على Finish (إنهاء).
  4. فعِّل المخطط عن طريق النقر على تفعيل.

الخطوة 2: إضافة هدف إلى Podfile

تأكَّد من إمكانية وصول الإضافة الجديدة إلى لوحة Firebase/Messaging من خلال إضافتها في Podfile:

  1. من المستكشف، افتح 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 كيلوبايت.