В этом руководстве описывается, как настроить Firebase Cloud Messaging в мобильных и веб-клиентских приложениях, чтобы вы могли надежно получать сообщения.
Получайте сообщения в приложении Flutter
Входящие сообщения обрабатываются по-разному в зависимости от состояния устройства. Чтобы понять эти сценарии и как интегрировать FCM в ваше приложение, сначала важно определить различные состояния, в которых может находиться устройство:
| Состояние | Описание |
|---|---|
| Передний план | Когда приложение открыто, видно и используется. |
| Фон | Приложение открыто, но находится в фоновом режиме (свёрнуто). Обычно это происходит, когда пользователь нажимает кнопку «Домой» на устройстве, переключается на другое приложение с помощью переключателя приложений или открывает приложение в другой вкладке (в веб-режиме). |
| Прекращено | Когда устройство заблокировано или приложение не запущено. |
Перед тем, как приложение сможет получать полезную нагрузку сообщений с помощью FCM, необходимо соблюсти несколько предварительных условий:
- Приложение должно быть открыто хотя бы один раз (для регистрации в FCM).
- В iOS, если пользователь смахивает приложение из переключателя приложений, его необходимо вручную открыть заново, чтобы фоновые сообщения снова заработали.
- На Android, если пользователь принудительно закрывает приложение через настройки устройства, его необходимо вручную открыть заново, чтобы сообщения начали работать.
- На веб-сайте вам необходимо запросить токен (с помощью
getToken()) с вашим сертификатом web push.
Запросить разрешение на получение сообщений
В iOS, macOS, веб-версии и Android 13 (или более поздней версии) перед получением полезных нагрузок FCM на вашем устройстве необходимо сначала запросить разрешение пользователя.
Пакет firebase_messaging предоставляет API для запроса разрешений с помощью метода requestPermission . Этот API принимает ряд именованных аргументов, определяющих тип запрашиваемых разрешений, например, могут ли сообщения, содержащие уведомления, воспроизводить звук или озвучивать сообщения с помощью 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 возвращают информацию о том, включено ли, отключено или не поддерживается определенное разрешение на текущем устройстве.
После получения разрешения и понимания различных типов состояний устройства ваше приложение может начать обрабатывать входящие полезные данные 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 отдельный изолят не требуется), что позволяет обрабатывать сообщения, даже когда приложение не запущено.
Есть несколько вещей, которые следует помнить о вашем фоновом обработчике сообщений:
- Это не должна быть анонимная функция.
- Это должна быть функция верхнего уровня (например, не метод класса, требующий инициализации).
- При использовании Flutter версии 3.3.0 или выше обработчик сообщений должен быть аннотирован с помощью
@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 :
// 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.
Локализовать сообщения
Вы можете отправлять локализованные строки двумя способами:
- Сохраните предпочитаемый язык каждого из ваших пользователей на вашем сервере и отправляйте персонализированные уведомления для каждого языка.
- Встраивайте локализованные строки в свое приложение и используйте встроенные региональные настройки операционной системы.
Вот как использовать второй метод:
Андроид
Укажите сообщения на языке по умолчанию в
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 позволяет анализировать данные с помощью BigQuery SQL, экспортировать их в другой облачный сервис или использовать их в собственных моделях машинного обучения. Экспорт в BigQuery включает все доступные данные сообщений, независимо от их типа и способа отправки: через API или через компоновщик уведомлений.
Чтобы включить экспорт, сначала следуйте инструкциям, приведенным в документе по экспорту данных BigQuery . Программное включение экспорта на уровне экземпляра приложения позволяет запрашивать у конечных пользователей разрешение на анализ данных о доставке сообщений (рекомендуется). Чтобы программно включить экспорт, выполните следующие инструкции:
Андроид
Вы можете использовать следующий код:
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
Интернет
Для веб-версии вам необходимо изменить сервис-воркер, чтобы использовать версию SDK v9. Версию v9 необходимо упаковать, поэтому для работы сервис-воркера вам понадобится сборщик, например esbuild . Чтобы узнать, как это сделать, см. пример приложения.
После перехода на SDK v9 вы можете использовать следующий код:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Не забудьте запустить yarn build , чтобы экспортировать новую версию вашего service worker в web папку.
Отображение изображений в уведомлениях на iOS
Чтобы на устройствах Apple во входящих уведомлениях FCM отображались изображения из полезной нагрузки FCM, необходимо добавить дополнительное расширение службы уведомлений и настроить приложение для его использования.
Если вы используете телефонную аутентификацию Firebase, вам необходимо добавить модуль Firebase Auth в свой Podfile.
Шаг 1 — Добавьте расширение службы уведомлений
- В Xcode нажмите Файл > Создать > Цель...
- В модальном окне появится список возможных целей; прокрутите или используйте фильтр, чтобы выбрать «Расширение службы уведомлений» . Нажмите «Далее» .
- Добавьте название продукта (используйте «ImageNotification» для следования инструкциям в этом руководстве), выберите
SwiftилиObjective-Cи нажмите «Готово» . - Включите схему, нажав Активировать .
Шаг 2 — Добавьте цель в Podfile
Быстрый
Убедитесь, что ваше новое расширение имеет доступ к пакету SWIFT FirebaseMessaging , добавив его в цель Runner :
В Навигаторе добавьте Firebase Apple platform SDK : Файл > Добавить зависимости пакета...
Найдите или введите URL-адрес пакета:
none https://github.com/firebase/firebase-ios-sdkДобавить в Project
Runner: Добавить пакетВыберите FirebaseMessaging и добавьте в целевой ImageNotification: Добавить пакет.
Objective-C
Убедитесь, что ваше новое расширение имеет доступ к модулю 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 installиз каталогаiosилиmacos.
Шаг 3 — Используйте помощника расширения
На этом этапе всё должно работать нормально. Последний шаг — вызов помощника расширения.
Быстрый
В навигаторе выберите расширение 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 — Добавьте изображение в полезную нагрузку
В полезную нагрузку уведомления теперь можно добавить изображение. Подробнее см. в статье «Как создать запрос на отправку» .