Nhận tin nhắn trong ứng dụng Flutter

Tùy thuộc vào trạng thái của thiết bị, tin nhắn đến sẽ được xử lý khác nhau. Để hiểu các tình huống này và cách tích hợp FCM vào ứng dụng của riêng bạn, điều quan trọng đầu tiên là phải thiết lập các trạng thái khác nhau của thiết bị:

Tình trạng Sự miêu tả
Vấn đề xung quanh Khi ứng dụng được mở, đang xem và đang sử dụng.
Lý lịch Khi ứng dụng đang mở nhưng ở chế độ nền (được thu nhỏ). Điều này thường xảy ra khi người dùng đã nhấn nút "home" trên thiết bị, đã chuyển sang một ứng dụng khác bằng trình chuyển đổi ứng dụng hoặc mở ứng dụng trong một tab khác (web).
Đã chấm dứt Khi thiết bị bị khóa hoặc ứng dụng không chạy.

Có một số điều kiện tiên quyết phải được đáp ứng trước khi ứng dụng có thể nhận tải trọng tin nhắn qua FCM:

  • Ứng dụng phải được mở ít nhất một lần (để cho phép đăng ký với FCM).
  • Trên iOS, nếu người dùng vuốt ứng dụng ra khỏi trình chuyển đổi ứng dụng, ứng dụng đó phải được mở lại theo cách thủ công để các thông báo nền bắt đầu hoạt động trở lại.
  • Trên Android, nếu người dùng buộc thoát ứng dụng khỏi cài đặt thiết bị, ứng dụng phải được mở lại theo cách thủ công để tin nhắn bắt đầu hoạt động.
  • Trên web, bạn phải yêu cầu mã thông báo (sử dụng getToken() ) với chứng chỉ đẩy web của mình.

Yêu cầu quyền nhận tin nhắn

Trên iOS, macOS, web và Android 13 (hoặc mới hơn), trước khi có thể nhận tải trọng FCM trên thiết bị của bạn, trước tiên bạn phải xin phép người dùng.

Gói firebase_messaging cung cấp một API đơn giản để yêu cầu quyền thông qua phương thức requestPermission . API này chấp nhận một số đối số được đặt tên xác định loại quyền bạn muốn yêu cầu, chẳng hạn như liệu tin nhắn chứa tải trọng thông báo có thể kích hoạt âm thanh hoặc đọc tin nhắn qua Siri hay không. Theo mặc định, phương thức này yêu cầu quyền mặc định hợp lý. API tham chiếu cung cấp tài liệu đầy đủ về mục đích của từng quyền.

Để bắt đầu, hãy gọi phương thức từ ứng dụng của bạn (trên iOS, phương thức gốc sẽ được hiển thị, trên web, luồng API gốc của trình duyệt sẽ được kích hoạt):

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}');

Thuộc authorizationStatus của đối tượng NotificationSettings được trả về từ yêu cầu có thể được sử dụng để xác định quyết định chung của người dùng:

  • authorized : Người dùng đã cấp quyền.
  • denied : Người dùng từ chối cấp phép.
  • notDetermined : Người dùng chưa chọn có cấp quyền hay không.
  • provisional : Người dùng được cấp quyền tạm thời

Các thuộc tính khác trên NotificationSettings trả về xem một quyền cụ thể được bật, tắt hay không được hỗ trợ trên thiết bị hiện tại.

Sau khi đã cấp quyền và hiểu rõ các loại trạng thái thiết bị khác nhau, giờ đây ứng dụng của bạn có thể bắt đầu xử lý các tải trọng FCM đến.

Xử lý tin nhắn

Dựa trên trạng thái hiện tại của ứng dụng, tải trọng đến của các loại thông báo khác nhau yêu cầu cách triển khai khác nhau để xử lý chúng:

Tin nhắn ở nền trước

Để xử lý tin nhắn trong khi ứng dụng của bạn ở nền trước, hãy nghe luồng 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}');
  }
});

Luồng chứa RemoteMessage , nêu chi tiết nhiều thông tin khác nhau về tải trọng, chẳng hạn như nó đến từ đâu, ID duy nhất, thời gian gửi, liệu nó có chứa thông báo hay không và hơn thế nữa. Vì tin nhắn được truy xuất trong khi ứng dụng của bạn ở nền trước, nên bạn có thể truy cập trực tiếp vào trạng thái và ngữ cảnh của ứng dụng Flutter.

Tin nhắn tiền cảnh và thông báo

Tin nhắn thông báo xuất hiện khi ứng dụng ở nền trước sẽ không hiển thị thông báo hiển thị theo mặc định, trên cả Android và iOS. Tuy nhiên, có thể ghi đè hành vi này:

  • Trên Android, bạn phải tạo kênh thông báo "Mức độ ưu tiên cao".
  • Trên iOS, bạn có thể cập nhật các tùy chọn trình bày cho ứng dụng.

Tin nhắn nền

Quá trình xử lý tin nhắn nền khác nhau trên nền tảng gốc (Android và Apple) và nền tảng web.

Nền tảng Apple và Android

Xử lý thông báo nền bằng cách đăng ký trình xử lý onBackgroundMessage . Khi nhận được tin nhắn, một lệnh cách ly sẽ được tạo ra (chỉ dành cho Android, iOS/macOS không yêu cầu cách ly riêng) cho phép bạn xử lý tin nhắn ngay cả khi ứng dụng của bạn không chạy.

Có một số điều cần lưu ý về trình xử lý tin nhắn nền của bạn:

  1. Nó không được là một chức năng ẩn danh.
  2. Nó phải là một hàm cấp cao nhất (ví dụ: không phải là một phương thức lớp yêu cầu khởi tạo).
  3. Khi sử dụng Flutter phiên bản 3.3.0 trở lên, trình xử lý thông báo phải được chú thích bằng @pragma('vm:entry-point') ngay phía trên phần khai báo hàm (nếu không, nó có thể bị xóa trong quá trình rung cây cho chế độ phát hành).
@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());
}

Vì trình xử lý chạy riêng biệt bên ngoài ngữ cảnh ứng dụng của bạn nên không thể cập nhật trạng thái ứng dụng hoặc thực thi bất kỳ logic nào tác động đến giao diện người dùng. Tuy nhiên, bạn có thể thực hiện logic như yêu cầu HTTP, thực hiện các thao tác IO (ví dụ: cập nhật bộ nhớ cục bộ), giao tiếp với các plugin khác, v.v.

Bạn cũng nên hoàn thành logic của mình càng sớm càng tốt. Việc chạy các tác vụ chuyên sâu, dài sẽ ảnh hưởng đến hiệu suất của thiết bị và có thể khiến HĐH chấm dứt quá trình. Nếu tác vụ chạy lâu hơn 30 giây, thiết bị có thể tự động tắt tiến trình.

Web

Trên Web, hãy viết một JavaScript Service Worker chạy ở chế độ nền. Sử dụng nhân viên dịch vụ để xử lý các tin nhắn nền.

Để bắt đầu, hãy tạo một tệp mới trong thư mục web của bạn và gọi nó là 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);
});

Tệp phải nhập cả SDK ứng dụng và SDK nhắn tin, khởi chạy Firebase và hiển thị biến messaging .

Tiếp theo, người lao động phải được đăng ký. Trong tệp nhập, sau khi tệp main.dart.js được tải, hãy đăng ký nhân viên của bạn:

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

Tiếp theo khởi động lại ứng dụng Flutter của bạn. Nhân viên sẽ được đăng ký và mọi tin nhắn nền sẽ được xử lý thông qua tệp này.

Xử lý tương tác

Vì thông báo là một tín hiệu hiển thị nên người dùng thường tương tác với chúng (bằng cách nhấn). Hành vi mặc định trên cả Android và iOS là mở ứng dụng. Nếu ứng dụng bị chấm dứt, nó sẽ được bắt đầu; nếu nó ở chế độ nền, nó sẽ được đưa lên nền trước.

Tùy thuộc vào nội dung của thông báo, bạn có thể muốn xử lý tương tác của người dùng khi ứng dụng mở ra. Ví dụ: nếu một tin nhắn trò chuyện mới được gửi qua thông báo và người dùng nhấn vào nó, bạn có thể muốn mở cuộc trò chuyện cụ thể khi ứng dụng mở ra.

Gói firebase-messaging cung cấp hai cách để xử lý tương tác này:

  • getInitialMessage() : Nếu ứng dụng được mở từ trạng thái kết thúc, Future chứa RemoteMessage sẽ được trả về. Sau khi sử dụng, RemoteMessage sẽ bị xóa.
  • onMessageOpenedApp : Stream đăng RemoteMessage khi ứng dụng được mở ở trạng thái nền.

Bạn nên xử lý cả hai trường hợp để đảm bảo trải nghiệm người dùng mượt mà. Ví dụ mã bên dưới phác thảo cách đạt được điều này:

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("...");
  }
}

Cách bạn xử lý tương tác tùy thuộc vào thiết lập ứng dụng của bạn. Ví dụ trên minh họa cơ bản bằng cách sử dụng StatefulWidget.

Bản địa hóa tin nhắn

Bạn có thể gửi các chuỗi được bản địa hóa theo hai cách khác nhau:

  • Lưu trữ ngôn ngữ ưa thích của từng người dùng trong máy chủ của bạn và gửi thông báo tùy chỉnh cho từng ngôn ngữ
  • Nhúng các chuỗi đã bản địa hóa vào ứng dụng của bạn và tận dụng cài đặt ngôn ngữ gốc của hệ điều hành

Đây là cách sử dụng phương pháp thứ hai:

Android

  1. Chỉ định thông báo bằng ngôn ngữ mặc định của bạn trong resources/values/strings.xml :

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Chỉ định các tin nhắn đã dịch trong thư mục values- language . Ví dụ: chỉ định tin nhắn tiếng Pháp trong resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Trong tải trọng của máy chủ, thay vì sử dụng các phím title , messagebody , hãy sử dụng title_loc_keybody_loc_key cho tin nhắn đã bản địa hóa của bạn và đặt chúng thành thuộc tính name của tin nhắn bạn muốn hiển thị.

    Tải trọng tin nhắn sẽ trông như thế này:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      }
    }
    

iOS

  1. Chỉ định tin nhắn ngôn ngữ mặc định của bạn trong Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Chỉ định các tin nhắn đã dịch trong thư mục language .lproj . Ví dụ: chỉ định tin nhắn tiếng Pháp trong fr.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Bonjour le monde";
    "NOTIFICATION_MESSAGE" = "C'est un message";
    

    Tải trọng tin nhắn sẽ trông như thế này:

    {
      "data": {
        "title_loc_key": "NOTIFICATION_TITLE",
        "body_loc_key": "NOTIFICATION_MESSAGE"
      }
    }
    

Bật xuất dữ liệu gửi tin nhắn

Bạn có thể xuất dữ liệu tin nhắn của mình sang BigQuery để phân tích thêm. BigQuery cho phép bạn phân tích dữ liệu bằng BigQuery SQL, xuất dữ liệu sang nhà cung cấp đám mây khác hoặc sử dụng dữ liệu cho các mô hình ML tùy chỉnh của bạn. Quá trình xuất sang BigQuery bao gồm tất cả dữ liệu có sẵn cho tin nhắn, bất kể loại tin nhắn hay tin nhắn có được gửi qua API hay trình soạn thảo Thông báo hay không.

Để bật tính năng xuất, trước tiên hãy làm theo các bước được mô tả ở đây , sau đó làm theo các hướng dẫn sau:

Android

Bạn có thể sử dụng đoạn mã sau:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

Đối với iOS, bạn cần thay đổi AppDelegate.m với nội dung sau.

#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

Đối với Web, bạn cần thay đổi nhân viên dịch vụ của mình để sử dụng phiên bản SDK v9. Phiên bản v9 cần được đóng gói, vì vậy bạn cần sử dụng một trình đóng gói như esbuild chẳng hạn để nhân viên dịch vụ hoạt động. Xem ứng dụng ví dụ để biết cách đạt được điều này.

Sau khi di chuyển sang SDK v9, bạn có thể sử dụng mã sau:

import {
  experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
  getMessaging,
} from 'firebase/messaging/sw';
...

const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);

Đừng quên chạy lệnh yarn build để xuất phiên bản mới của nhân viên dịch vụ của bạn sang thư mục web .

Hiển thị hình ảnh trong thông báo trên iOS

Trên các thiết bị Apple, để Thông báo FCM đến hiển thị hình ảnh từ tải trọng FCM, bạn phải thêm tiện ích mở rộng dịch vụ thông báo bổ sung và định cấu hình ứng dụng của mình để sử dụng tiện ích đó.

Nếu bạn đang sử dụng xác thực điện thoại Firebase, bạn phải thêm nhóm Xác thực Firebase vào Podfile của mình.

Bước 1 - Thêm tiện ích mở rộng dịch vụ thông báo

  1. Trong Xcode, nhấp vào Tệp > Mới > Mục tiêu...
  2. Một phương thức sẽ trình bày một danh sách các mục tiêu có thể có; cuộn xuống hoặc sử dụng bộ lọc để chọn Tiện ích mở rộng dịch vụ thông báo . Bấm tiếp .
  3. Thêm tên sản phẩm (sử dụng "ImageNotification" để làm theo hướng dẫn này), đặt ngôn ngữ thành Objective-C và nhấp vào Hoàn tất .
  4. Kích hoạt chương trình bằng cách nhấp vào Kích hoạt .

Bước 2 - Thêm mục tiêu vào Podfile

Đảm bảo rằng tiện ích mở rộng mới của bạn có quyền truy cập vào nhóm Firebase/Messaging bằng cách thêm nó vào Podfile:

  1. Từ Bộ điều hướng, mở Podfile: Pods > Podfile

  2. Cuộn xuống cuối tập tin và thêm:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. Cài đặt hoặc cập nhật nhóm của bạn bằng cách sử dụng pod install từ thư mục ios hoặc macos .

Bước 3 - Sử dụng trình trợ giúp tiện ích mở rộng

Tại thời điểm này, mọi thứ vẫn sẽ chạy bình thường. Bước cuối cùng là gọi trình trợ giúp mở rộng.

  1. Từ trình điều hướng, chọn tiện ích mở rộng ImageNotification của bạn

  2. Mở tệp NotificationService.m .

  3. Ở đầu tệp, nhập FirebaseMessaging.h ngay sau NotificationService.h như hiển thị bên dưới.

    Thay thế nội dung của NotificationService.m bằng:

    #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
    

Bước 4 - Thêm hình ảnh vào tải trọng

Trong tải trọng thông báo của bạn, bây giờ bạn có thể thêm hình ảnh. Xem tài liệu iOS về cách tạo yêu cầu gửi . Hãy nhớ rằng kích thước hình ảnh tối đa 300KB được thiết bị thực thi.