| בחירת פלטפורמה: | iOS+ Android Web Flutter Unity C++ |
הטיפול בהודעות נכנסות משתנה בהתאם למצב המכשיר. כדי להבין את התרחישים האלה ואיך לשלב את FCM באפליקציה שלכם, חשוב קודם להבין את המצבים השונים שבהם המכשיר יכול להיות:
| מדינה | תיאור |
|---|---|
| חזית | כשהאפליקציה פתוחה, מוצגת ונמצאת בשימוש. |
| רקע | כשהאפליקציה פתוחה, אבל ברקע (מוזערת). זה קורה בדרך כלל כשהמשתמש לוחץ על כפתור הבית במכשיר, עובר לאפליקציה אחרת באמצעות הממשק למעבר בין חשבונות או פותח את האפליקציה בכרטיסייה אחרת (באינטרנט). |
| הסגירה | כשהמכשיר נעול או כשהאפליקציה לא פועלת. |
כדי שהאפליקציה תוכל לקבל תוכן של הודעות באמצעות FCM, צריך לעמוד בכמה תנאים מוקדמים:
- האפליקציה צריכה להיפתח לפחות פעם אחת (כדי לאפשר רישום ב-FCM).
- ב-iOS, אם המשתמש מחליק את האפליקציה מהממשק למעבר בין חשבונות, צריך לפתוח אותה מחדש באופן ידני כדי שההודעות ברקע יתחילו לפעול שוב.
- ב-Android, אם המשתמש סוגר ידנית את האפליקציה מהגדרות המכשיר, צריך לפתוח אותה מחדש באופן ידני כדי שההודעות יתחילו לפעול.
- באינטרנט, צריך לבקש אסימון (באמצעות
getToken()) עם אישור ההתראה באינטרנט.
בקשת הרשאה לקבלת הודעות
ב-iOS, ב-macOS, באינטרנט וב-Android מגרסה 13 (או גרסה חדשה יותר), לפני שתוכלו לקבל מטענים ייעודיים (payloads) של FCM במכשיר, תצטרכו לבקש את הרשאת המשתמש.
חבילת firebase_messaging מספקת API לבקשת הרשאה באמצעות ה-method requestPermission. ה-API הזה מקבל כמה ארגומנטים עם שם שמגדירים את סוג ההרשאות שרוצים לבקש, למשל אם הודעות שמכילות מטענים ייעודיים (payloads) של התראות יכולות להפעיל צליל או להקריא הודעות באמצעות Siri. כברירת מחדל, השיטה מבקשת הרשאות ברירת מחדל הגיוניות. ב-API של ההרשאות יש תיעוד מלא של כל הרשאה.
כדי להתחיל, קוראים לשיטה מהאפליקציה (ב-iOS יוצג חלון מודאלי מובנה, ובאינטרנט יופעל תהליך ה-API של הדפדפן):
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 מחזירים את המצב של הרשאה ספציפית במכשיר הנוכחי: מופעלת, מושבתת או לא נתמכת.
אחרי שניתנה הרשאה והבנתם את הסוגים השונים של מצבי המכשיר, האפליקציה יכולה להתחיל לטפל במטענים הייעודיים (payloads) הנכנסים של FCM.
טיפול בהודעות
בהתאם למצב הנוכחי של האפליקציה, מטען ייעודי (payload) נכנס של סוגי הודעות שונים דורש הטמעות שונות כדי לטפל בהם:
הודעות בחזית
כדי לטפל בהודעות כשהאפליקציה בחזית, צריך להאזין לסטרימינג של 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.
פלטפורמות של אפל ו-Android
כדי לטפל בהודעות ברקע, צריך לרשום handler של onBackgroundMessage. כשמתקבלות הודעות, נוצרת סביבה מבודדת (רק ב-Android, לא ב-iOS/macOS) שמאפשרת לטפל בהודעות גם כשהאפליקציה לא פועלת.
יש כמה דברים שכדאי לזכור לגבי הפונקציה לטיפול בהודעות ברקע:
- היא לא יכולה להיות פונקציה אנונימית.
- היא חייבת להיות פונקציה ברמה העליונה (למשל, לא שיטה של מחלקה שדורשת הפעלה).
- כשמשתמשים ב-Flutter בגרסה 3.3.0 ומעלה, צריך להוסיף את ההערה
@pragma('vm:entry-point')ל-message handler ממש מעל להצהרת הפונקציה (אחרת יכול להיות שהיא תוסר במהלך tree shaking במצב release).
@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());
}
מכיוון שה-handler פועל בבידוד משלו מחוץ להקשר של האפליקציה, אי אפשר לעדכן את מצב האפליקציה או להפעיל לוגיקה שמשפיעה על ממשק המשתמש. עם זאת, אפשר לבצע פעולות לוגיות כמו בקשות HTTP, לבצע פעולות קלט/פלט (למשל, עדכון האחסון המקומי), לתקשר עם פלאגינים אחרים וכו'.
מומלץ גם להשלים את הלוגיקה בהקדם האפשרי. הפעלת משימות ארוכות ואינטנסיביות משפיעה על ביצועי המכשיר, ויכולה לגרום למערכת ההפעלה להפסיק את התהליך. אם המשימות פועלות במשך יותר מ-30 שניות, יכול להיות שהמכשיר יפסיק את התהליך באופן אוטומטי.
אינטרנט
באינטרנט, כותבים Service Worker ב-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.
בשלב הבא, צריך לרשום את ה-worker. בקובץ index.html, רושמים את ה-worker על ידי שינוי התג <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>
אם אתם עדיין משתמשים במערכת התבניות הישנה, אתם יכולים לרשום את ה-worker על ידי שינוי התג <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. ה-worker יירשם וכל ההודעות ברקע יטופלו באמצעות הקובץ הזה.
טיפול באינטראקציה
ההתראות הן רמז חזותי, ולכן משתמשים נוטים לקיים איתן אינטראקציה (למשל, ללחוץ עליהן). פעולת ברירת המחדל גם ב-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.
התאמה לשוק המקומי של Messages
אפשר לשלוח מחרוזות מותאמות לשוק המקומי בשתי דרכים שונות:
- שומרים בשרת את השפה המועדפת של כל אחד מהמשתמשים ושולחים התראות בהתאמה אישית לכל שפה
- הטמעת מחרוזות מותאמות לשפה המקומית באפליקציה ושימוש בהגדרות המובנות של הלוקאל במערכת ההפעלה
כך משתמשים בשיטה השנייה:
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>במטען הייעודי (payload) של השרת, במקום להשתמש במפתחות
title,messageו-body, משתמשים ב-title_loc_keyוב-body_loc_keyלהודעה המותאמת לשוק המקומי, ומגדירים אותם למאפייןnameשל ההודעה שרוצים להציג.מטען הייעודי (payload) של ההודעה ייראה כך:
{ "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";מטען הייעודי (payload) של ההודעה ייראה כך:
{ "apns": { "payload": { "alert": { "title-loc-key": "NOTIFICATION_TITLE", "loc-key": "NOTIFICATION_MESSAGE" } } } }
הפעלת ייצוא של נתוני מסירת הודעות
אפשר לייצא את נתוני ההודעות ל-BigQuery כדי לבצע ניתוח נוסף. ב-BigQuery אתם יכולים לנתח את הנתונים באמצעות BigQuery SQL, לייצא אותם לספק שירותי ענן אחר או להשתמש בהם במודלים של למידת מכונה בהתאמה אישית. ייצוא ל-BigQuery כולל את כל הנתונים שזמינים לגבי הודעות, ללא קשר לסוג ההודעה או לאופן השליחה שלה – באמצעות API או הכלי ליצירת הודעות.
כדי להפעיל את הייצוא, קודם צריך לבצע את השלבים שמפורטים במסמך ייצוא נתונים ל-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
אינטרנט
באינטרנט, צריך לשנות את ה-service worker כדי להשתמש בגרסה 9 של ה-SDK. צריך לארוז את גרסה 9, אז צריך להשתמש בכלי אריזה כמו esbuild כדי שקובץ השירות יעבוד. באפליקציה לדוגמה מוסבר איך לעשות את זה.
אחרי שמעבירים את האפליקציה ל-SDK בגרסה 9, אפשר להשתמש בקוד הבא:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
אל תשכחו להריץ את yarn build כדי לייצא את הגרסה החדשה של ה-service worker לתיקייה web.
הצגת תמונות בהתראות ב-iOS
במכשירי Apple, כדי שהתראות FCM נכנסות יציגו תמונות ממטען הייעודי (payload) של FCM, צריך להוסיף תוסף נוסף של שירות התראות ולהגדיר את האפליקציה כך שתשתמש בו.
אם אתם משתמשים באימות טלפוני ב-Firebase, אתם צריכים להוסיף את ה-pod של Firebase Auth ל-Podfile.
שלב 1 – הוספת תוסף לשירות ההתראות
- ב-Xcode, לוחצים על File (קובץ) > New (חדש) > Target… (יעד…).
- בחלון מודאלי תוצג רשימה של יעדים אפשריים. גוללים אל Notification Service Extension או משתמשים במסנן כדי לבחור באפשרות הזו. לוחצים על Next.
- מוסיפים שם מוצר (אפשר להשתמש ב-ImageNotification כדי לפעול לפי ההוראות במדריך הזה), בוחרים באפשרות
SwiftאוObjective-Cולוחצים על סיום. - כדי להפעיל את הסכימה, לוחצים על הפעלה.
שלב 2 – מוסיפים את היעד ל-Podfile
Swift
מוודאים שלתוסף החדש יש גישה לחבילת FirebaseMessaging swift
על ידי הוספתו ליעד Runner:
ב-Navigator, מוסיפים את Firebase Apple platforms SDK: File > Add Package Dependencies... (קובץ > הוספת יחסי תלות בין חבילות...)
חיפוש או הזנה של כתובת URL של חבילה:
none https://github.com/firebase/firebase-ios-sdkהוספה לפרויקט
Runner: הוספת חבילהבוחרים באפשרות FirebaseMessaging ומוסיפים אותה ליעד ImageNotification: Add Package (הוספת חבילה)
Objective-C
כדי לוודא שלתוסף החדש יש גישה ל-pod של 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מתקינים או מעדכנים את ה-pods באמצעות
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 – הוספת התמונה למטען הייעודי (payload)
עכשיו אפשר להוסיף תמונה למטען הייעודי (payload) של ההתראה. איך יוצרים בקשת שליחה