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

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

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

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

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

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

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

توفر حزمة 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 ما إذا كان قد تم تمكين إذن معين أو تعطيله أو عدم دعمه على الجهاز الحالي.

بمجرد منح الإذن وفهم الأنواع المختلفة لحالة الجهاز، يمكن لتطبيقك الآن البدء في التعامل مع حمولات 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 الخاص بك.

رسائل المقدمة والإخطار

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

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

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

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

منصات أبل وأندرويد

التعامل مع رسائل الخلفية عن طريق تسجيل معالج onBackgroundMessage . عند تلقي الرسائل، يتم إنشاء عزل (Android فقط، ولا يتطلب 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());
}

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

يوصى أيضًا بإكمال المنطق الخاص بك في أسرع وقت ممكن. يؤثر تشغيل المهام الطويلة والمكثفة على أداء الجهاز وقد يتسبب في إنهاء نظام التشغيل للعملية. إذا تم تشغيل المهام لمدة أطول من 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.

توطين الرسائل

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

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

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

ذكري المظهر

  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"
      }
    }
    

دائرة الرقابة الداخلية

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

لتمكين التصدير، اتبع أولاً الخطوات الموضحة هنا ، ثم اتبع الإرشادات التالية:

ذكري المظهر

يمكنك استخدام الكود التالي:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

دائرة الرقابة الداخلية

بالنسبة لنظام التشغيل 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 على سبيل المثال لتشغيل عامل الخدمة. راجع التطبيق المثال لمعرفة كيفية تحقيق ذلك.

بمجرد الترحيل إلى الإصدار 9 SDK، يمكنك استخدام التعليمة البرمجية التالية:

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

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

لا تنس تشغيل yarn build لتصدير الإصدار الجديد من عامل الخدمة الخاص بك إلى مجلد web .

عرض الصور في الإشعارات على iOS

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

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

الخطوة 1 - إضافة ملحق خدمة الإعلام

  1. في Xcode، انقر فوق ملف > جديد > الهدف...
  2. سيقدم النموذج قائمة بالأهداف المحتملة؛ قم بالتمرير لأسفل أو استخدم عامل التصفية لتحديد ملحق خدمة الإشعارات . انقر فوق {التالي .
  3. أضف اسم منتج (استخدم "ImageNotification" لمتابعة هذا البرنامج التعليمي)، واضبط اللغة على Objective-C، ثم انقر فوق "إنهاء" .
  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 كيلو بايت.