| اختيار المنصة: | iOS+ Android الويب Flutter Unity C++ |
تتم معالجة الرسائل الواردة بشكل مختلف حسب حالة الجهاز. لفهم هذه السيناريوهات وكيفية دمج FCM في تطبيقك، من المهم أولاً تحديد الحالات المختلفة التي يمكن أن يكون عليها الجهاز:
| الولاية | الوصف |
|---|---|
| التطبيق في المقدّمة | عندما يكون التطبيق مفتوحًا ومرئيًا ويتم استخدامه |
| الخلفية | عندما يكون التطبيق مفتوحًا، ولكن في الخلفية (مصغّرًا) يحدث ذلك عادةً عندما يضغط المستخدم على زر "الشاشة الرئيسية" على الجهاز، أو ينتقل إلى تطبيق آخر باستخدام أداة التبديل بين التطبيقات، أو يفتح التطبيق في علامة تبويب مختلفة (على الويب). |
| تم إنهاؤها | عندما يكون الجهاز مقفلاً أو لا يكون التطبيق قيد التشغيل |
هناك بعض الشروط المسبقة التي يجب استيفاؤها قبل أن يتمكّن التطبيق من تلقّي حمولات الرسائل باستخدام FCM:
- يجب أن يكون التطبيق قد تم فتحه مرة واحدة على الأقل (للسماح بالتسجيل في FCM).
- على أجهزة iOS، إذا أزال المستخدم التطبيق من مبدّل التطبيقات، يجب إعادة فتحه يدويًا حتى تبدأ الرسائل التي يتم إرسالها في الخلفية بالعمل مرة أخرى.
- على أجهزة Android، إذا فرض المستخدم إغلاق التطبيق من إعدادات الجهاز، يجب إعادة فتحه يدويًا لتبدأ الرسائل في العمل.
- على الويب، يجب أن تكون قد طلبت رمزًا مميزًا (باستخدام
getToken()) مع شهادة الإشعارات الفورية على الويب.
طلب الإذن بتلقّي الرسائل
On iOS, macOS, web and Android 13 (or newer), before FCM payloads can be received on your device, you must first ask the user's permission.
توفّر حزمة 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: منح المستخدم إذنًا مؤقتًا
The other properties on NotificationSettings return whether a specific
permission is enabled, disabled or not supported on the current device.
بعد منح الإذن وفهم الأنواع المختلفة لحالة الجهاز، يمكن لتطبيقك الآن البدء في معالجة حمولات 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، وتنفيذ عمليات إدخال/إخراج (مثل تعديل مساحة التخزين المحلية)، والتواصل مع المكوّنات الإضافية الأخرى، وما إلى ذلك.
ننصحك أيضًا بإكمال منطقك في أقرب وقت ممكن. قد يؤثر تنفيذ مهام طويلة ومكثّفة في أداء الجهاز، وقد يتسبّب في إيقاف نظام التشغيل للعملية. إذا استغرقت المهام أكثر من 30 ثانية، قد يوقف الجهاز العملية تلقائيًا.
الويب
على الويب، اكتب Service Worker بلغة JavaScript، وهو برنامج يعمل في الخلفية. استخدِم مشغّل الخدمات لمعالجة الرسائل في الخلفية.
للبدء، أنشئ ملفًا جديدًا في دليل 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
حدِّد رسائلك باللغة التلقائية في
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للرسالة التي تريد عرضها.ستبدو حمولة الرسالة على النحو التالي:
{ "android": { "notification": { "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";ستبدو حمولة الرسالة على النحو التالي:
{ "apns": { "payload": { "alert": { "title-loc-key": "NOTIFICATION_TITLE", "loc-key": "NOTIFICATION_MESSAGE" } } } }
تفعيل تصدير بيانات تسليم الرسائل
يمكنك تصدير بيانات رسائلك إلى BigQuery لإجراء المزيد من التحليلات. تتيح لك BigQuery تحليل البيانات باستخدام لغة SQL في BigQuery، أو تصديرها إلى مقدّم خدمة سحابية آخر، أو استخدام البيانات لنماذج تعلُّم الآلة المخصّصة. يتضمّن التصدير إلى 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 لتشغيل مشغّل الخدمات. اطّلِع على تطبيق المثال لمعرفة كيفية تحقيق ذلك.
بعد الانتقال إلى حزمة SDK الإصدار 9، يمكنك استخدام الرمز التالي:
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: إضافة إضافة خدمة الإشعارات
- في Xcode، انقر على ملف (File) > جديد (New) > هدف (Target)…
- ستظهر قائمة بالأهداف المحتملة في نافذة مشروطة. انتقِل إلى Notification Service Extension أو استخدِم الفلتر لاختياره. انقر على التالي.
- أضِف اسم منتج (استخدِم "ImageNotification" لمتابعة هذا الدليل التعليمي)، واختَر
SwiftأوObjective-C، ثم انقر على إنهاء. - فعِّل المخطط بالنقر على تفعيل.
الخطوة 2: إضافة الهدف إلى Podfile
Swift
تأكَّد من أنّ الإضافة الجديدة يمكنها الوصول إلى حزمة FirebaseMessaging swift
من خلال إضافتها إلى هدف Runner:
من Navigator، أضِف حزمة تطوير البرامج (SDK) لمنصات Apple من Firebase: ملف > إضافة موارد الاعتمادية للحزمة... (File > Add Package Dependencies...).
ابحث عن عنوان URL للحزمة أو أدخِله:
none https://github.com/firebase/firebase-ios-sdkالإضافة إلى المشروع
Runner: إضافة حزمةاختَر FirebaseMessaging وأضِفه إلى ImageNotification المستهدَف: إضافة حزمة
Objective-C
تأكَّد من أنّ الإضافة الجديدة يمكنها الوصول إلى حزمة Firebase/Messaging من خلال
إضافتها في Podfile:
من Navigator، افتح 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 installمن دليلiosأوmacos.
الخطوة 3: استخدام أداة مساعدة الإضافة
في هذه المرحلة، من المفترض أن يستمر كل شيء في العمل بشكل طبيعي. الخطوة الأخيرة هي استدعاء مساعد الإضافة.
Swift
من المستكشف، اختَر إضافة ImageNotification
افتح ملف
NotificationService.swift.استبدِل محتوى
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
من المستكشف، اختَر إضافة ImageNotification
افتح ملف
NotificationService.m.في أعلى الملف، استورِد
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: إضافة الصورة إلى الحمولة
يمكنك الآن إضافة صورة في حمولة الإشعار. لمزيد من المعلومات، يُرجى الاطّلاع على كيفية إنشاء طلب إرسال.