Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

在 Flutter 應用中接收消息

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

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

狀態描述
前景當應用程序打開、處於查看和使用狀態時。
背景當應用程序打開但在後台(最小化)時。這通常發生在用戶按下設備上的“主頁”按鈕、使用應用程序切換器切換到另一個應用程序或在不同選項卡(Web)中打開應用程序時。
終止當設備被鎖定或應用程序未運行時。

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

  • 該應用程序必須至少打開過一次(以允許在 FCM 註冊)。
  • 在 iOS 上,如果用戶將應用程序從應用程序切換器上滑開,則必須手動重新打開它,背景消息才能再次開始工作。
  • 在 Android 上,如果用戶從設備設置中強制退出應用程序,則必須手動重新打開它才能使消息開始工作。
  • 在 Web 上,您必須使用 Web 推送證書請求令牌(使用getToken() )。

請求接收消息的權限(Apple 和 Web)

在 iOS、macOS 和 Web 上,在您的設備上接收 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. 它必須在函數聲明的正上方用@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());
}

由於處理程序在您的應用程序上下文之外獨立運行,因此無法更新應用程序狀態或執行任何影響 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文件,註冊您的 worker:

<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 :當應用程序從後台狀態打開時發布RemoteMessageStream

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

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 分析數據,將其導出到另一個雲提供商,或將數據用於您的自定義 ML 模型。向 BigQuery 的導出包括消息的所有可用數據,無論消息類型如何或消息是通過 API 還是通知編輯器發送的。

要啟用導出,請先按照此處描述的步驟操作,然後按照以下說明操作:

安卓

您可以使用以下代碼: dart 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文件夾。