В зависимости от состояния устройства входящие сообщения обрабатываются по-разному. Чтобы понять эти сценарии и способы интеграции 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 секунд, устройство может автоматически завершить процесс.
Интернет
В Интернете напишите Service Worker JavaScript, который работает в фоновом режиме. Используйте сервис-воркера для обработки фоновых сообщений.
Для начала создайте новый файл в своем 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. Рабочий будет зарегистрирован, и любые фоновые сообщения будут обрабатываться через этот файл.
Обработка взаимодействия
Поскольку уведомления являются видимыми сигналами, пользователи обычно взаимодействуют с ними (нажимая). Поведение по умолчанию как на 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 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
Интернет
В Интернете вам необходимо сменить своего сервис-воркера, чтобы использовать версию SDK v9. Версия v9 должна быть в комплекте, поэтому вам нужно использовать упаковщик, например, esbuild
чтобы заставить работать сервис-воркера. Посмотрите пример приложения , чтобы узнать, как этого добиться.
После перехода на SDK v9 вы можете использовать следующий код:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Не забудьте запустить yarn build
, чтобы экспортировать новую версию вашего сервис-воркера в web
папку.
Отображение изображений в уведомлениях на iOS
На устройствах Apple, чтобы входящие уведомления FCM отображали изображения из полезных данных FCM, необходимо добавить дополнительное расширение службы уведомлений и настроить приложение для его использования.
Если вы используете аутентификацию по телефону Firebase, вам необходимо добавить модуль Firebase Auth в свой подфайл.
Шаг 1. Добавьте расширение службы уведомлений
- В Xcode нажмите Файл > Создать > Цель...
- Модальное окно представит список возможных целей; прокрутите вниз или воспользуйтесь фильтром, чтобы выбрать «Расширение службы уведомлений» . Нажмите Далее .
- Добавьте название продукта (используйте «ImageNotification», чтобы следовать этому руководству), установите язык Objective-C и нажмите «Готово» .
- Включите схему, нажав «Активировать» .
Шаг 2. Добавьте цель в подфайл.
Убедитесь, что ваше новое расширение имеет доступ к модулю Firebase/Messaging
, добавив его в подфайл:
В навигаторе откройте подфайл: «Модули» > «Подфайл».
Прокрутите файл до конца и добавьте:
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 КБ установлен устройством.