在 Flutter 應用程式中接收訊息

根據設備的狀態,傳入訊息的處理方式有所不同。要了解這些場景以及如何將 FCM 整合到您自己的應用程式中,首先重要的是確定設備可以處於的各種狀態:

狀態描述
前景當應用程式開啟、查看和使用時。
背景當應用程式開啟但在背景(最小化)時。當使用者按下裝置上的「主頁」按鈕、使用應用程式切換器切換到另一個應用程式或在不同的標籤(Web)中開啟應用程式時,通常會發生這種情況。
終止當設備鎖定或應用程式未運行時。

在應用程式可以透過 FCM 接收訊息有效負載之前,必須滿足一些先決條件:

  • 該申請必須至少開啟過一次(以允許在 FCM 註冊)。
  • 在 iOS 上,如果用戶從應用程式切換器上滑開應用程序,則必須手動重新開啟應用程序,後台訊息才能再次開始工作。
  • 在 Android 上,如果用戶從裝置設定中強制退出應用程序,則必須手動重新開啟應用程序,訊息才能開始工作。
  • 在網路上,您必須使用網路推送憑證請求令牌(使用getToken() )。

請求接收訊息的權限

在 iOS、macOS、Web 和 Android 13(或更高版本)上,您必須先徵求使用者的許可,然後才能在裝置上接收 FCM 負載。

firebase_messaging套件提供了一個簡單的 API,用於透過requestPermission方法請求權限。此 API 接受許多命名參數,這些參數定義您想要要求的權限類型,例如包含通知負載的訊息傳遞是否可以觸發聲音或透過 Siri 讀出訊息。預設情況下,該方法請求合理的預設權限。參考 API 提供了有關每個權限用途的完整文件。

首先,從您的應用程式呼叫該方法(在 iOS 上,將顯示本機模式,在 Web 上,將觸發瀏覽器的本機 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}');

從請求傳回的NotificationSettings物件的authorizationStatus屬性可用來決定使用者的總體決定:

  • 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 ,詳細說明有關負載的各種信息,例如負載的來源、唯一 ID、發送時間、是否包含通知等。由於訊息是在您的應用程式位於前台時檢索的,因此您可以直接存取 Flutter 應用程式的狀態和上下文。

前台消息和通知消息

預設情況下,在 Android 和 iOS 上,當應用程式位於前台時到達的通知訊息不會顯示可見的通知。但是,可以覆寫此行為:

  • 在 Android 上,您必須建立「高優先級」通知頻道。
  • 在 iOS 上,您可以更新應用程式的演示選項。

背景訊息

在本機(Android 和 Apple)和基於 Web 的平台上,處理後台訊息的過程是不同的。

蘋果平台與安卓平台

透過註冊onBackgroundMessage處理程序來處理後台訊息。收到訊息時,會產生隔離(僅限 Android,iOS/macOS 不需要單獨的隔離),即使應用程式未執行,您也可以處理訊息。

關於後台訊息處理程序,需要記住以下幾點:

  1. 它不能是匿名函數。
  2. 它必須是頂級函數(例如,不是需要初始化的類別方法)。
  3. 當使用 Flutter 版本 3.3.0 或更高版本時,訊息處理程序必須在函數宣告正上方使用@pragma('vm:entry-point')進行註解(否則它可能會在發布模式的tree shake 過程中被刪除)。
@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());
}

由於處理程序在應用程式上下文之外以自己的隔離方式運行,因此無法更新應用程式狀態或執行任何影響 UI 的邏輯。但是,您可以執行 HTTP 請求等邏輯、執行 IO 操作(例如更新本機儲存)、與其他外掛程式通訊等。

也建議盡快完成你的邏輯。運行長時間、密集的任務會影響設備效能,並可能導致作業系統終止進程。如果任務運行時間超過 30 秒,設備可能會自動終止該進程。

網路

在 Web 上,編寫一個在背景執行的 JavaScript Service Worker 。使用 Service Worker 來處理後台訊息。

首先,在您的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() :如果應用程式從終止狀態打開,將傳回包含RemoteMessageFuture 。一旦使用, RemoteMessage將被刪除。
  • onMessageOpenedApp :當應用程式從背景狀態開啟時發布RemoteMessage Stream

建議處理這兩種情況,以確保為用戶提供流暢的用戶體驗。下面的程式碼範例概述瞭如何實現這一點:

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. 在伺服器負載中,不要使用titlemessagebody鍵,而是使用title_loc_keybody_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

網路

對於 Web,您需要變更 Service Worker 才能使用 v9 版本的 SDK。 v9 版本需要捆綁,因此您需要使用像esbuild這樣的捆綁器來讓 Service Worker 運作。請參閱範例應用程式以了解如何實現此目的。

遷移到 v9 SDK 後,您可以使用以下程式碼:

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 pod 新增至 Podfile。

第 1 步 - 新增通知服務擴展

  1. 在 Xcode 中,按一下檔案 > 新建 > 目標...
  2. 模式將呈現可能目標的清單;向下捲動或使用過濾器選擇“通知服務擴充功能” 。點擊下一步
  3. 新增產品名稱(在本教程中使用「ImageNotification」),將語言設定為 Objective-C,然後按一下「完成」
  4. 按一下「啟動」啟用該方案。

步驟 2 - 將目標新增至 Podfile

透過將新擴充功能新增至 Podfile 中,確保您的新擴充功能可以存取Firebase/Messaging pod:

  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. 使用iosmacos目錄中的pod install安裝或更新 pod。

第 3 步 - 使用擴充助手

此時,一切應該仍然正常運作。最後一步是呼叫擴充助手。

  1. 從導航器中選擇您的 ImageNotification 擴充功能

  2. 開啟NotificationService.m檔。

  3. 在檔案頂部,在NotificationService.h之後導入FirebaseMessaging.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 文件以了解如何建立傳送請求。請記住,設備強制規定最大影像大小為 300KB。