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

В зависимости от состояния устройства входящие сообщения обрабатываются по-разному. Чтобы понять эти сценарии и способы интеграции 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 секунд, устройство может автоматически завершить процесс.

Интернет

В Интернете напишите Service Worker JavaScript, который работает в фоновом режиме. Используйте сервис-воркера для обработки фоновых сообщений.

Для начала создайте новый файл в своем web каталоге и назовите его firebase-messaging-sw.js :

importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.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 .

Далее работника необходимо зарегистрировать. Во входном файле после загрузки файла main.dart.js зарегистрируйте своего работника:

<html>
<body>
  ...
  <script src="main.dart.js" type="application/javascript"></script>
  <script>
       if ('serviceWorker' in navigator) {
          // Service workers are supported. Use them.
          window.addEventListener('load', function () {
            // ADD THIS LINE
            navigator.serviceWorker.register('/firebase-messaging-sw.js');

            // 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;

            //  ...
          });
      }
  </script>

Затем перезапустите приложение 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.

Локализация сообщений

Вы можете отправлять локализованные строки двумя разными способами:

  • Сохраняйте предпочтительный язык каждого из ваших пользователей на своем сервере и отправляйте индивидуальные уведомления для каждого языка.
  • Встраивайте локализованные строки в свое приложение и используйте собственные настройки локали операционной системы.

Вот как использовать второй метод:

Андроид

  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 сообщения, которое вы хотите отобразить.

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

    {
      "data": {
        "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";
    

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

    {
      "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. Добавьте расширение службы уведомлений

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

Шаг 2. Добавьте цель в подфайл.

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

  1. В навигаторе откройте подфайл: «Модули» > «Подфайл».

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

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

Шаг 3. Используйте помощник расширения

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

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

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

  3. В верхней части файла импортируйте 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 КБ установлен устройством.