В зависимости от состояния устройства входящие сообщения обрабатываются по-разному. Чтобы понять эти сценарии и как интегрировать FCM в собственное приложение, сначала важно установить различные состояния, в которых может находиться устройство:
Состояние | Описание |
---|---|
Передний план | Когда приложение открыто, видно и используется. |
Фон | Когда приложение открыто, но находится в фоновом режиме (свернуто). Обычно это происходит, когда пользователь нажал кнопку «Домой» на устройстве, переключился на другое приложение с помощью переключателя приложений или открыл приложение на другой вкладке (веб). |
Прекращено | Когда устройство заблокировано или приложение не запущено. |
Прежде чем приложение сможет получать полезные данные сообщений через FCM, необходимо выполнить несколько предварительных условий:
- Приложение должно быть открыто хотя бы один раз (для возможности регистрации в FCM).
- В iOS, если пользователь смахивает приложение из переключателя приложений, его необходимо вручную открыть заново, чтобы фоновые сообщения снова заработали.
- На Android, если пользователь принудительно закрывает приложение в настройках устройства, его необходимо вручную открыть заново, чтобы сообщения начали работать.
- На веб-сайте вам необходимо запросить токен (используя
getToken()
) с вашим веб-сертификатом 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());
}
Поскольку обработчик работает в своем собственном изоляте вне контекста вашего приложения, невозможно обновить состояние приложения или выполнить какую-либо логику, влияющую на UI. Однако вы можете выполнять логику, такую как HTTP-запросы, выполнять операции ввода-вывода (например, обновление локального хранилища), взаимодействовать с другими плагинами и т. д.
Также рекомендуется завершить логику как можно скорее. Выполнение длительных, интенсивных задач влияет на производительность устройства и может привести к завершению процесса ОС. Если задачи выполняются дольше 30 секунд, устройство может автоматически завершить процесс.
Веб
В Интернете напишите JavaScript Service Worker , который работает в фоновом режиме. Используйте service worker для обработки фоновых сообщений.
Для начала создайте новый файл в вашем web
каталоге и назовите его firebase-messaging-sw.js
:
// Please see this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/master/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. Worker будет зарегистрирован, и все фоновые сообщения будут обрабатываться через этот файл.
Взаимодействие с обработкой
Поскольку уведомления являются видимым сигналом, пользователи обычно взаимодействуют с ними (нажимая). Поведение по умолчанию как на Android, так и на iOS — открыть приложение. Если приложение завершено, оно будет запущено; если оно находится в фоновом режиме, оно будет выведено на передний план.
В зависимости от содержания уведомления вы можете захотеть обработать взаимодействие пользователя при открытии приложения. Например, если новое сообщение чата отправлено через уведомление и пользователь нажимает на него, вы можете захотеть открыть определенный разговор при открытии приложения.
Пакет firebase-messaging
предоставляет два способа обработки этого взаимодействия:
-
getInitialMessage()
: Если приложение открывается из завершенного состояния, то будет возвращенFuture
, содержащийRemoteMessage
. После использованияRemoteMessage
будет удален. -
onMessageOpenedApp
:Stream
, который отправляетRemoteMessage
при открытии приложения из фонового состояния.
Рекомендуется обрабатывать оба сценария, чтобы обеспечить плавный UX для ваших пользователей. Пример кода ниже показывает, как этого можно добиться:
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.
Локализовать сообщения
Вы можете отправлять локализованные строки двумя способами:
- Сохраните предпочитаемый язык каждого из ваших пользователей на вашем сервере и отправляйте индивидуальные уведомления для каждого языка.
- Встраивайте локализованные строки в свое приложение и используйте собственные настройки локали операционной системы.
Вот как использовать второй метод:
андроид
Укажите сообщения на языке по умолчанию в
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
сообщения, которое вы хотите отобразить.Полезная нагрузка сообщения будет выглядеть следующим образом:
{ "data": { "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";
Полезная нагрузка сообщения будет выглядеть следующим образом:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_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
Веб
Для Web вам нужно изменить свой service worker, чтобы использовать версию v9 SDK. Версия v9 должна быть связана, поэтому вам нужно использовать bundler, например esbuild
чтобы заставить service worker работать. Посмотрите пример приложения , чтобы увидеть, как этого добиться.
После перехода на 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 нажмите Файл > Создать > Цель...
- В модальном окне будет представлен список возможных целей; прокрутите вниз или используйте фильтр, чтобы выбрать Notification Service Extension . Нажмите Next .
- Добавьте название продукта (используйте «ImageNotification» для следования инструкциям этого руководства), установите язык Objective-C и нажмите «Готово» .
- Включите схему, нажав «Активировать» .
Шаг 2 — Добавьте цель в Podfile
Убедитесь, что ваше новое расширение имеет доступ к модулю 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.m
.В верхней части файла импортируйте
FirebaseMessaging.h
сразу послеNotificationService.h
, как показано ниже.Замените содержимое
NotificationService.m
на:#import "NotificationService.h" #import "FirebaseMessaging.h" #import "FirebaseAuth.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 () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) 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 КБ принудительно устанавливается устройством.