Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

Flutter 앱에서 메시지 수신

장치의 상태에 따라 수신 메시지가 다르게 처리됩니다. 이러한 시나리오와 FCM을 자체 애플리케이션에 통합하는 방법을 이해하려면 먼저 기기가 있을 수 있는 다양한 상태를 설정하는 것이 중요합니다.

상태 설명
전경 응용 프로그램이 열려 있고, 보고 있고, 사용 중일 때.
배경 애플리케이션이 열려 있지만 백그라운드에 있을 때(최소화됨). 이는 일반적으로 사용자가 기기의 "홈" 버튼을 누르거나, 앱 전환기를 사용하여 다른 앱으로 전환하거나, 다른 탭(웹)에서 애플리케이션을 연 경우에 발생합니다.
종료됨 기기가 잠겨 있거나 애플리케이션이 실행되고 있지 않을 때.

애플리케이션이 FCM을 통해 메시지 페이로드를 수신하기 전에 충족되어야 하는 몇 가지 전제 조건이 있습니다.

  • FCM에 등록할 수 있도록 애플리케이션을 한 번 이상 열어야 합니다.
  • iOS에서 사용자가 앱 전환기에서 애플리케이션을 스와이프하면 백그라운드 메시지가 다시 작동하려면 수동으로 다시 열어야 합니다.
  • Android에서 사용자가 기기 설정에서 앱을 강제 종료한 경우 메시지가 작동하려면 앱을 수동으로 다시 열어야 합니다.
  • 웹에서는 웹 푸시 인증서로 토큰을 요청했어야 합니다( getToken() 사용).

메시지 수신 권한 요청(Apple 및 Web)

iOS, macOS 및 웹에서 기기에서 FCM 페이로드를 수신하려면 먼저 사용자의 허가를 받아야 합니다.

firebase_messaging 패키지는 requestPermission 메서드를 통해 권한을 요청하기 위한 간단한 API를 제공합니다. 이 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}');

요청에서 반환된 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}');
  }
});

스트림에는 페이로드의 출처, 고유 ID, 전송 시간, 알림 포함 여부 등과 같은 페이로드에 대한 다양한 정보를 자세히 설명하는 RemoteMessage 가 포함되어 있습니다. 애플리케이션이 포그라운드에 있는 동안 메시지가 검색되었으므로 Flutter 애플리케이션의 상태 및 컨텍스트에 직접 액세스할 수 있습니다.

전경 및 알림 메시지

애플리케이션이 포그라운드에 있는 동안 도착하는 알림 메시지는 기본적으로 Android 및 iOS 모두에서 눈에 보이는 알림을 표시하지 않습니다. 그러나 이 동작을 무시할 수 있습니다.

  • Android에서는 "높은 우선 순위" 알림 채널을 만들어야 합니다.
  • iOS에서는 애플리케이션의 프레젠테이션 옵션을 업데이트할 수 있습니다.

백그라운드 메시지

백그라운드 메시지를 처리하는 프로세스는 네이티브(Android 및 Apple) 및 웹 기반 플랫폼에서 다릅니다.

애플 플랫폼과 안드로이드

onBackgroundMessage 처리기를 등록하여 백그라운드 메시지를 처리합니다. 메시지가 수신되면 isolate가 생성되어(Android만 해당, iOS/macOS는 별도의 isolate가 필요하지 않음) 애플리케이션이 실행되지 않는 경우에도 메시지를 처리할 수 있습니다.

백그라운드 메시지 처리기에 대해 염두에 두어야 할 몇 가지 사항이 있습니다.

  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 작업(예: 로컬 저장소 업데이트)을 수행하고, 다른 플러그인과 통신할 수 있습니다.

또한 가능한 한 빨리 논리를 완성하는 것이 좋습니다. 길고 집약적인 작업을 실행하면 장치 성능에 영향을 미치고 OS가 프로세스를 종료할 수 있습니다. 작업이 30초 이상 실행되면 장치에서 자동으로 프로세스를 종료할 수 있습니다.

편물

웹에서 백그라운드에서 실행되는 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() : 응용 프로그램이 종료된 상태에서 열리면 RemoteMessage 를 포함하는 Future 가 반환됩니다. 소비되면 RemoteMessage 가 제거됩니다.
  • onMessageOpenedApp : 애플리케이션이 백그라운드 상태에서 열릴 때 RemoteMessage 를 게시하는 Stream 입니다.

사용자에게 원활한 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을 사용하는 기본 그림을 보여줍니다.

메시지 현지화

두 가지 방법으로 지역화된 문자열을 보낼 수 있습니다.

  • 각 사용자의 선호 언어를 서버에 저장하고 각 언어에 대한 사용자 지정 알림을 보냅니다.
  • 앱에 현지화된 문자열을 포함하고 운영 체제의 기본 로캘 설정을 활용합니다.

두 번째 방법을 사용하는 방법은 다음과 같습니다.

기계적 인조 인간

  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 , messagebody 키를 사용하는 대신 현지화된 메시지에 title_loc_keybody_loc_key 를 사용하고 표시하려는 메시지의 name 속성으로 설정합니다.

    메시지 페이로드는 다음과 같습니다.

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

아이폰 OS

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

아이폰 OS

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의 경우 v9 버전의 SDK를 사용하기 위해서는 서비스 워커를 변경해야 합니다. v9 버전은 번들로 제공되어야 하므로 예를 들어 서비스 워커가 작동하도록 하려면 esbuild 와 같은 번들러를 사용해야 합니다. 이를 달성하는 방법을 보려면 예제 앱 을 참조하십시오.

v9 SDK로 마이그레이션한 후에는 다음 코드를 사용할 수 있습니다.

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

...

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

서비스 워커의 새 버전을 web 폴더로 내보내려면 yarn build 를 실행하는 것을 잊지 마세요.