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

اختيار النظام الأساسي: iOS+ Android الويب Flutter Unity C++


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

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

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

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

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

على أجهزة 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 أو عمليات الإدخال والإخراج (مثل تعديل وحدة التخزين المحلية) أو التواصل مع مكوّنات إضافية أخرى وما إلى ذلك.

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

الويب

على الويب، اكتب عامل خدمة JavaScript Service Worker يتم تشغيله في الخلفية. استخدِم عامل الخدمة لمعالجة الرسائل في الخلفية.

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

// See this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/main/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. سيتم تسجيل عامل الخدمة وستتم معالجة أي رسائل في الخلفية باستخدام هذا الملف.

معالجة التفاعل

بما أنّ الإشعارات هي إشارات مرئية، من الشائع أن يتفاعل المستخدمون معها (من خلال الضغط عليها). السلوك التلقائي على كلٍّ من أجهزة 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 using 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 للرسالة التي تريد عرضها.

    ستبدو حمولة الرسالة على النحو التالي:

    {
      "android": {
         "notification": {
           "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";
    

    ستبدو حمولة الرسالة على النحو التالي:

    {
      "apns": {
         "payload": {
           "alert": {
             "title-loc-key": "NOTIFICATION_TITLE",
             "loc-key": "NOTIFICATION_MESSAGE"
           }
         }
      }
    }
    

تفعيل تصدير بيانات تسليم الرسائل

يمكنك تصدير بيانات رسائلك إلى BigQuery لإجراء المزيد من التحليلات. يتيح لك BigQuery تحليل البيانات باستخدام BigQuery SQL أو تصديرها إلى موفّر سحابة إلكترونية آخر أو استخدام البيانات لنماذج تعلُّم الآلة المخصّصة. يتضمّن التصدير إلى BigQuery جميع البيانات المتاحة للرسائل، بغض النظر عن نوع الرسالة أو ما إذا تم إرسالها باستخدام واجهة برمجة التطبيقات أو أداة إنشاء الإشعارات.

لتفعيل عملية التصدير، اتّبِع أولاً الخطوات الواردة في مستند تصدير البيانات إلى 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. يجب تجميع الإصدار 9، لذا عليك استخدام أداة تجميع مثل 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 Auth إلى Podfile.

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

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

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

Swift

تأكَّد من أنّ الإضافة الجديدة يمكنها الوصول إلى حزمة FirebaseMessaging في Swift من خلال إضافتها إلى هدف Runner:

  1. من "أداة التنقّل"، أضِف حزمة SDK لمنصّات Firebase على Apple: ملف > إضافة موارد الاعتمادية للحزمة...

  2. ابحث عن عنوان URL للحزمة أو أدخِله: none https://github.com/firebase/firebase-ios-sdk

  3. أضِف إلى المشروع Runner: إضافة حزمة

  4. اختَر FirebaseMessaging وأضِف إلى هدف ImageNotification: إضافة حزمة

Objective-C

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

  1. من "أداة التنقّل"، افتح Podfile: وحدات > 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: استخدام أداة مساعدة الإضافة

في هذه المرحلة، من المفترَض أن يستمر تشغيل كل شيء بشكلٍ طبيعي. الخطوة الأخيرة هي استدعاء أداة مساعدة الإضافة.

Swift

  1. من "أداة التنقّل"، اختَر إضافة ImageNotification

  2. افتح الملف NotificationService.swift.

  3. استبدِل محتوى NotificationService.swift بما يلي:

    import UserNotifications
    import FirebaseMessaging
    
    class NotificationService: UNNotificationServiceExtension {
    
        var contentHandler: ((UNNotificationContent) -> Void)?
        var bestAttemptContent: UNMutableNotificationContent?
    
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
            self.contentHandler = contentHandler
            bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
            Messaging.serviceExtension().populateNotificationContent(bestAttemptContent!, withContentHandler: contentHandler)
        }
    
        override func serviceExtensionTimeWillExpire() {
            if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
                contentHandler(bestAttemptContent)
            }
        }
    }
    

Objective-C

  1. من "أداة التنقّل"، اختَر إضافة ImageNotification

  2. افتح الملف NotificationService.m.

  3. في أعلى الملف، استورِد FirebaseMessaging.h مباشرةً بعد NotificationService.h.

    استبدِل محتوى NotificationService.m بما يلي:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import <FirebaseAuth/FirebaseAuth-Swift.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 () <NSURLSessionDelegate>
    
    @property(nonatomic) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property(nonatomic) 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: إضافة الصورة إلى الحمولة

في حمولة الإشعار، يمكنك الآن إضافة صورة. لمزيد من المعلومات، اطّلِع على كيفية إنشاء طلب إرسال.