Join us in person and online for Firebase Summit on October 18, 2022. Learn how Firebase can help you accelerate app development, release your app with confidence, and scale with ease. Register now

在 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. 它必須是頂級函數(例如,不是需要初始化的類方法)。
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 :當應用程序從後台狀態打開時發布RemoteMessageStream

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

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 的基本說明。