Получайте сообщения в приложениях Flutter.

Выберите платформу: 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 не требует отдельного изолированного объекта), что позволяет обрабатывать сообщения даже тогда, когда ваше приложение не запущено.

При разработке фонового обработчика сообщений следует учитывать несколько моментов:

  1. Это не должна быть анонимная функция.
  2. Это должна быть функция верхнего уровня (например, не метод класса, требующий инициализации).
  3. При использовании 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

  1. Укажите язык по умолчанию для сообщений в resources/values/strings.xml :

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Укажите переведенные сообщения в каталоге values- language . Например, укажите сообщения на французском языке в resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. В серверной части сообщения вместо ключей title , message и body используйте title_loc_key и body_loc_key для локализованного сообщения и присвойте им атрибут name сообщения, которое вы хотите отобразить.

    Содержимое сообщения будет выглядеть следующим образом:

    {
      "android": {
         "notification": {
           "title_loc_key": "notification_title",
           "body_loc_key": "notification_message"
         }
      }
    }
    

iOS

  1. Укажите язык по умолчанию для ваших сообщений в Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Укажите переведенные сообщения в каталоге 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 . Программное включение экспорта на уровне экземпляра приложения позволяет запрашивать у конечных пользователей разрешение на анализ данных о доставке их сообщений (рекомендуется). Следуйте этим инструкциям, чтобы программно включить экспорт:

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 входящих уведомлений необходимо добавить дополнительное расширение службы уведомлений и настроить приложение для его использования.

Если вы используете аутентификацию по телефону через Firebase, необходимо добавить модуль Firebase Auth в ваш Podfile.

Шаг 1 — Добавьте расширение службы уведомлений

  1. В Xcode нажмите File > New > Target...
  2. В появившемся всплывающем окне будет представлен список возможных целей; прокрутите список или воспользуйтесь фильтром, чтобы выбрать «Расширение службы уведомлений» . Нажмите « Далее» .
  3. Добавьте название продукта (используйте "ImageNotification", чтобы следовать инструкциям в этом руководстве), выберите Swift или Objective-C и нажмите "Готово" .
  4. Активируйте схему, нажав кнопку «Активировать» .

Шаг 2 — Добавьте цель в Podfile

Быстрый

Убедитесь, что ваше новое расширение имеет доступ к пакету FirebaseMessaging в Swift, добавив его в целевой объект Runner :

  1. В панели навигации добавьте Firebase Apple Platforms SDK : Файл > Добавить зависимости пакета...

  2. Найдите или введите URL-адрес пакета: none https://github.com/firebase/firebase-ios-sdk

  3. Добавить в Project Runner : Добавить пакет

  4. Выберите FirebaseMessaging и добавьте в целевой объект ImageNotification: Add Package.

Objective-C

Убедитесь, что ваше новое расширение имеет доступ к поду Firebase/Messaging , добавив его в Podfile:

  1. В панели навигации откройте файл Podfile: Pods > Podfile

  2. Перейдите в конец файла и добавьте:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. Установите или обновите ваши модули Pods с помощью pod install из каталога ios или macos .

Шаг 3 — Воспользуйтесь вспомогательной программой расширения.

На этом этапе всё должно работать нормально. Последний шаг — вызов вспомогательной функции расширения.

Быстрый

  1. В меню навигации выберите расширение ImageNotification.

  2. Откройте файл NotificationService.swift .

  3. Замените содержимое файла 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

  1. В меню навигации выберите расширение ImageNotification.

  2. Откройте файл NotificationService.m .

  3. В самом начале файла, сразу после файла NotificationService.h , импортируйте FirebaseMessaging.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 — Добавьте изображение в полезную нагрузку

Теперь в содержимое уведомления можно добавить изображение. Подробнее см. в разделе «Как создать запрос на отправку» .