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

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

الويب

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

للبدء، أنشئ ملفًا جديدًا في دليل 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. سيتم تسجيل العامل وسيتم التعامل مع أي رسائل في الخلفية عبر هذا الملف.

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

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