| Выберите платформу: | iOS+ Android Веб Flutter Unity C++ |
В зависимости от состояния устройства входящие сообщения обрабатываются по-разному. Чтобы понять, как интегрировать 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());
}
Поскольку обработчик выполняется в собственном изолированном процессе вне контекста приложения, он не может обновлять состояние приложения или выполнять логику, влияющую на интерфейс. Однако вы можете выполнять логические операции, например отправлять 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 приложения и 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.
Как локализовать сообщения
Отправлять локализованные строки можно двумя способами:
- Храните на сервере информацию о предпочитаемом языке каждого пользователя и отправляйте уведомления на этом языке.
- Встройте в приложение локализованные строки и используйте встроенные в операционную систему настройки языка.
Вот как это сделать:
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>В полезной нагрузке сервера вместо ключей
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 позволяет анализировать данные с помощью SQL BigQuery, экспортировать их в другое облачное хранилище или использовать для собственных моделей машинного обучения. При экспорте в 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
Веб-приложение
Чтобы использовать версию 9 SDK на сайте, вам нужно изменить сервис-воркер. Версия 9 должна быть в комплекте, поэтому для работы service worker вам понадобится сборщик, например 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, добавьте в Podfile модуль Firebase Auth.
Шаг 1. Добавьте расширение сервиса уведомлений
- В Xcode нажмите File (Файл) > New (Создать) > Target (Цель).
- В модальном окне появится список возможных целей. Прокрутите его или используйте фильтр, чтобы выбрать Расширение сервиса уведомлений. Нажмите Далее.
- Добавьте название продукта (в этом руководстве используется ImageNotification), выберите
SwiftилиObjective-Cи нажмите Готово. - Чтобы включить схему, нажмите Активировать.
Шаг 2. Добавьте цель в Podfile
Swift
Убедитесь, что у нового расширения есть доступ к пакету FirebaseMessaging swift
для этого добавьте его в целевой объект Runner:
В навигаторе добавьте Firebase SDK для платформ Apple: File (Файл) > Add Package Dependencies (Добавить зависимости пакетов).
Введите запрос или URL пакета:
none https://github.com/firebase/firebase-ios-sdkДобавить в проект
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. Используйте помощника по расширениям
На этом этапе все должно работать нормально. Последний шаг – вызвать вспомогательную функцию расширения.
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. Добавьте изображение в полезную нагрузку
Теперь в полезную нагрузку уведомления можно добавить изображение. Подробнее о том, как создать запрос на отправку…