| Выберите платформу: | iOS+ Android Web 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 приложения и обмена сообщениями, инициализировать 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 позволяет анализировать данные с помощью BigQuery SQL, экспортировать их другому поставщику облачных сервисов или использовать для собственных моделей ML. Экспорт в 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 необходимо упаковать, поэтому для работы сервисного работника вам нужно использовать упаковщик, например esbuild. Пример реализации можно посмотреть в примере приложения.
После перехода на SDK версии 9 вы можете использовать следующий код:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Не забудьте запустить yarn build, чтобы экспортировать новую версию сервисного работника в папку web.
Как показывать изображения в уведомлениях на iOS
Чтобы на устройствах Apple в уведомлениях FCM показывались изображения из полезной нагрузки FCM, необходимо добавить расширение службы уведомлений и настроить его использование в приложении.
Если вы используете аутентификацию по номеру телефона в Firebase, добавьте Firebase Auth pod в Podfile.
Шаг 1. Добавьте расширение сервиса уведомлений
- В Xcode нажмите File (Файл) > New (Создать) > Target (Цель).
- В модальном окне появится список возможных целей. Прокрутите его или воспользуйтесь фильтром, чтобы выбрать Notification Service Extension (Расширение сервиса уведомлений). Нажмите Далее.
- Добавьте название товара (в этом руководстве используется ImageNotification), выберите
SwiftилиObjective-Cи нажмите Finish (Готово). - Нажмите Активировать.
Шаг 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: 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Установите или обновите поды с помощью
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. Добавьте изображение в полезную нагрузку
Теперь в полезную нагрузку уведомления можно добавить изображение. Подробнее о том, как создать запрос на отправку…