بناءً على حالة الجهاز، يتم التعامل مع الرسائل الواردة بشكل مختلف. لفهم هذه السيناريوهات وكيفية دمج ميزة "المراسلة عبر السحابة الإلكترونية من Firebase" في تطبيقك، يجب أولاً تحديد الحالات المختلفة التي قد يكون فيها الجهاز:
الولاية | الوصف |
---|---|
العناصر الأمامية | عندما يكون التطبيق مفتوحًا ومعروضًا وقيد الاستخدام |
الخلفية | عند فتح التطبيق ولكن في الخلفية (مصغر). يحدث ذلك عادةً عندما يضغط المستخدم على زر "الشاشة الرئيسية" على الجهاز، أو عندما ينتقل إلى تطبيق آخر باستخدام أداة تبديل التطبيقات، أو عندما يكون التطبيق مفتوحًا في علامة تبويب مختلفة (على الويب). |
تم إنهاء | عندما يكون الجهاز مقفلاً أو التطبيق غير مفعَّل |
هناك بعض الشروط المسبقة التي يجب استيفاؤها قبل أن يتمكن التطبيق من استلام حمولات الرسائل عبر خدمة "المراسلة عبر السحابة الإلكترونية من Firebase":
- يجب أن يكون قد تم فتح التطبيق مرة واحدة على الأقل (للسماح بالتسجيل باستخدام "نظام إرسال الرسائل من Google").
- على نظام التشغيل 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) والمنصّات المستندة إلى الويب.
أنظمة التشغيل Apple وAndroid
يمكنك معالجة الرسائل التي تعمل في الخلفية من خلال تسجيل معالِج onBackgroundMessage
. عند استلام الرسائل، يتم إنشاء مثيل
معزول (لنظام التشغيل Android فقط، ولا يتطلب iOS/macOS مثيلًا معزولًا منفصلاً)، ما يتيح لك معالجة الرسائل حتى عندما لا يكون تطبيقك قيد التشغيل.
في ما يلي بعض النقاط التي يجب أخذها في الاعتبار بشأن معالِج الرسائل في الخلفية:
- يجب ألا تكون دالة مجهولة الهوية.
- يجب أن تكون دالة من المستوى الأعلى (مثلاً، لا تكون طريقة فئة تتطلّب الإعداد).
- عند استخدام الإصدار 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، وتنفيذ عمليات I/O (مثل تعديل مساحة التخزين المحلية)، والتواصل مع الإضافات الأخرى، وما إلى ذلك.
يُوصى أيضًا بإكمال المنطق في أقرب وقت ممكن. يؤثر تشغيل مهام طويلة ومجهدة على أداء الجهاز وقد يتسبب في إنهاء نظام التشغيل للعملية. إذا استمر تنفيذ المهام لأكثر من 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
حدِّد رسائل اللغة التلقائية في
resources/values/strings.xml
:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
حدِّد الرسائل المترجمة في دليل
values-language
. على سبيل المثال، تحديد الرسائل الفرنسية فيresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
في الحمولة على الخادم، بدلاً من استخدام المفاتيح
title
وmessage
وbody
، استخدِمtitle_loc_key
وbody_loc_key
لرسالتك المترجَمة، واضبطهما على سمةname
للرسالة التي تريد عرضها.ستظهر حمولة الرسالة على النحو التالي:
{ "data": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } }
iOS
حدِّد الرسائل باللغة التلقائية باللغة
Base.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
حدِّد الرسائل المترجمة في دليل
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 تحليل البيانات باستخدام لغة الاستعلامات البنيوية (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).
يجب تجميع الإصدار v9 في حزمة، لذا عليك استخدام أداة تجميع مثل esbuild
كي يعمل مشغّل الخدمات.
يمكنك الاطّلاع على مثال التطبيق لمعرفة كيفية تحقيق ذلك.
بعد نقل البيانات إلى الإصدار 9 من حزمة تطوير البرامج (SDK)، يمكنك استخدام الرمز التالي:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
لا تنسَ تشغيل yarn build
لتصدير الإصدار الجديد من worker الخدمة إلى مجلد web
.
عرض الصور في الإشعارات على أجهزة iOS
على أجهزة Apple، لكي يتمكّن تطبيقك من عرض الصور الواردة من حمولة إطار عمل Firebase للرسائل في إشعارات FCM، عليك إضافة إضافة إضافية لخدمة الإشعارات وضبط تطبيقك لاستخدامها.
إذا كنت تستخدم مصادقة الهاتف من Firebase، يجب إضافة لوحة مصادقة Firebase إلى Podfile.
الخطوة 1: إضافة إضافة خدمة إشعارات
- في Xcode، انقر على ملف > جديد > هدف...
- سيعرِض النموذج قائمة بالأهداف المحتملة. انتقِل للأسفل أو استخدِم الفلتر لاختيار إضافة خدمة الإشعارات. انقر على التالي.
- أضِف اسم منتج (استخدِم "ImageNotification" لمتابعة هذا الدليل التعليمي)، واضبط اللغة على Objective-C، ثم انقر على إنهاء.
- فعِّل المخطّط بالنقر على تفعيل.
الخطوة 2: إضافة الهدف إلى Podfile
تأكَّد من إمكانية وصول الإضافة الجديدة إلى لوحة Firebase/Messaging
من خلال إضافتها في Podfile:
من "المستكشف"، افتح Podfile: Pods > Podfile
انتقِل إلى أسفل الملف وأضِف ما يلي:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
ثبِّت وحدات pod أو عدِّلها باستخدام
pod install
من دليلios
أوmacos
.
الخطوة 3: استخدام أداة مساعدة الإضافات
في هذه المرحلة، من المفترض أن يظلّ كل شيء يعمل على النحو المعتاد. الخطوة الأخيرة هي استدعاء مساعد الإضافة.
من المستكشف، اختَر إضافة ImageNotification.
افتح ملف
NotificationService.m
.في أعلى الملف، استورِد
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 كيلوبايت.