הטיפול בהודעות נכנסות משתנה בהתאם למצב המכשיר. כדי להבין את התרחישים האלה ואיך לשלב את FCM באפליקציה שלכם, חשוב קודם להבין את המצבים השונים שבהם יכול להיות מכשיר:
מדינה | תיאור |
---|---|
Foreground | כשהאפליקציה פתוחה, מוצגת ופעילה. |
רקע | כשהאפליקציה פתוחה, אבל ברקע (מוזערת). זה קורה בדרך כלל כשהמשתמש לוחץ על לחצן 'הבית' במכשיר, עובר לאפליקציה אחרת באמצעות מרכז האפליקציות או שהאפליקציה פתוחה בכרטיסייה אחרת (באינטרנט). |
הסתיים | כשהמכשיר נעול או כשהאפליקציה לא פועלת. |
יש כמה תנאים מוקדמים שצריכים להתקיים כדי שהאפליקציה תוכל לקבל נתוני הודעות דרך FCM:
- צריך לפתוח את האפליקציה לפחות פעם אחת (כדי לאפשר הרשמה ל-FCM).
- ב-iOS, אם המשתמש מחליק את האפליקציה מתוך מחליף האפליקציות, צריך לפתוח אותה מחדש באופן ידני כדי שהודעות הרקע יתחילו לפעול שוב.
- ב-Android, אם המשתמש סוגר את האפליקציה בכוח דרך הגדרות המכשיר, צריך לפתוח אותה מחדש באופן ידני כדי שההודעות יתחילו לפעול.
- באינטרנט, צריך לבקש אסימון (באמצעות
getToken()
) עם אישור הפוש לאתר.
שליחת בקשה להרשאה לקבל הודעות
ב-iOS, macOS, באינטרנט וב-Android 13 (או בגרסאות חדשות יותר), לפני שאפשר לקבל מטען ייעודי (payload) של FCM במכשיר, צריך לבקש קודם את הרשאת המשתמש.
חבילת firebase_messaging
מספקת API פשוט לבקשת הרשאה באמצעות השיטה requestPermission
.
ה-API הזה מקבל מספר ארגומנטים עם שמות שמגדירים את סוג ההרשאות שרוצים לבקש, למשל אם הודעות שמכילות מטען ייעודי (payload) של התראות יכולות להפעיל צליל או להקריא הודעות באמצעות 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.
טיפול בהודעות
בהתאם למצב הנוכחי של האפליקציה, כדי לטפל במטענים ייעודיים (payloads) נכנסים של סוגי הודעות שונים, צריך להטמיע פתרונות שונים:
הודעות שמוצגות בחזית
כדי לטפל בהודעות בזמן שהאפליקציה פועלת בחזית, צריך להאזין לזרם 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')
מעל הצהרת הפונקציה של 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
:
// Please 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 כדי לרשום את ה-worker:
<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 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>
במטען הייעודי (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, לייצא אותם לספק ענן אחר או להשתמש בהם במודלים מותאמים אישית של ML. ייצוא ל-BigQuery כולל את כל הנתונים שזמינים לגבי הודעות, ללא קשר לסוג ההודעה או לאופן השליחה שלה – דרך ה-API או דרך הכלי ליצירת הודעות.
כדי להפעיל את הייצוא, קודם צריך לפעול לפי השלבים שמתוארים כאן, ואז לפעול לפי ההוראות הבאות:
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 יציגו תמונות מתוך מטען הנתונים של 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
target:
בסרגל הניווט, מוסיפים את Firebase Apple platforms SDK: File > Add Package Dependencies...
מחפשים או מזינים את כתובת ה-URL של החבילה:
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
ב-Navigator, בוחרים את התוסף 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
ב-Navigator, בוחרים את התוסף 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) של ההתראה. בתיעוד של iOS מוסבר איך יוצרים בקשת שליחה. חשוב לזכור שהמכשיר מגביל את גודל התמונה ל-300KB.