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