В этом руководстве описывается, как настроить 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 или через компоновщик уведомлений.
Чтобы включить экспорт, сначала следуйте инструкциям в документе «Понимание поставки» , а затем следуйте этим инструкциям:
Андроид
Вы можете использовать следующий код:
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 — Добавьте изображение в полезную нагрузку
В полезную нагрузку уведомления теперь можно добавлять изображение. Подробнее о создании запроса на отправку см . в документации iOS. Имейте в виду, что устройство ограничивает максимальный размер изображения 300 КБ.