1. Package 설치

flutter pub add firebase_messaging

 

2. 앱이 종료되었거나, 백그라운드에 있을때 메시지 수신하고, 백그라운드에서 특정 작업을 수행하는 로직을 처리한다.

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();

  debugPrint("----------------------------");
  debugPrint("background message Title: ${message.notification!.title}");
  debugPrint("background message Body: ${message.notification!.body}");
  debugPrint("----------------------------");
}

class NotiMain extends StatefulWidget {
  const NotiMain({super.key});

  @override
  State<NotiMain> createState() => _NotiMain();
}

class _NotiMain extends State<NotiMain> {
  @override
  void initState() {
    super.initState();

    // Background/Terminated message handler 등록
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    // 메시지 리스너 등록
    _initFirebaseMessaging();
  }

...

FirebaseMessaging.onBackgroundMessage는 앱이 실행 중이 아닐 때 푸시 알림을 받고 그에 따른 특정 동작을 수행할 수 있게 해주는 핵심 메소드이다.

아래에서 설명할 onMessageOpenedApp.listen 메소드 또한 백그라운드 메시지 수신을 처리하는데,

차이는

onBackgroundMessage 는 데이터 처리용으로 사용자 클릭이 불필요하고,

onMessageOpenedApp.listen 는 사용자와의 상호작용을 위한 것으로 클릭을 필요로한다는 차이가 있다.

 

참고로 @pragma('vm:entry-point') 어노테이션은

Dart 컴파일러가 앱의 최종 빌드 크기를 줄이기 위해 최적화 과정으로 불필요한 코드를 제거하게 하는데

Dart 컴파일러에게 해당 함수를 제거하지 않도록 지지하는 명령어이다.

즉 특정 코드가 런타임에 의해 제거되지 않도록 보호하는 역할을 한다.

이런 처리가 필요한 함수들은 

  1. 백그라운드 서비스 (Background Services): Flutter 앱이 종료된 상태에서 OS(운영체제)가 특정 Dart 함수를 호출해야 할 때.

  2. 플랫폼 채널 (Platform Channels): 네이티브 코드(Kotlin/Swift)에서 Dart 코드를 호출할 때.

  3. JSON 직렬화/역직렬화 (Serialization): 특정 라이브러리(예: json_serializable)에서 생성된 코드.

 

3. 앱이 열려있거나 백그라운드 일때 메시지 수신 처리한다.

...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
...
    );
  }

  Future<void> _initFirebaseMessaging() async {
    FirebaseMessaging fcm = FirebaseMessaging.instance;

    // 알림 권한이 승인된 후에만 성공적으로 토큰을 발급받을 수 있다.
    NotificationSettings settings = await fcm.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

    debugPrint('User granted permissions: ${settings.authorizationStatus}');

    String? _token = await fcm.getToken();
    debugPrint('----------------------------------------------');
    debugPrint('FCM Token: $_token');
    debugPrint('----------------------------------------------');

    // 토큰 갱신 모니터링
    fcm.onTokenRefresh.listen((newToken) {
      _token = newToken;
      debugPrint('FCM new Token: $_token');
    });

    // Foreground 메시지 수신 처리 (앱이 열려있는 상태)
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      debugPrint('----------------------------------------------');
      debugPrint('Foreground Message');
      debugPrint('----------------------------------------------');
      
      if (message.notification != null) {
        _showMessage('Foreground', message.notification!);
      }
    });

    // Background 메시지 수신처리 (앱이 백그라운드 상태)
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      debugPrint('----------------------------------------------');
      debugPrint('Background Message');
      debugPrint('----------------------------------------------');

      if (message.notification != null) {
        _showMessage('Background', message.notification!);
      }
    });

    // 앱이 완전히 종료된 상태에서 사용자 알림을 클릭하여 앱이 실행될 때 수신된 FCM 메시지 데이터를 가저온다.
    RemoteMessage? message = await fcm.getInitialMessage();

    if (message != null) {
      debugPrint('----------------------------------------------');
      debugPrint('Terminated Message');
      debugPrint('----------------------------------------------');

      if (message.notification != null) {
        _showMessage('Terminated', message.notification!);
      }
    }
  }

  void _showMessage(String type, RemoteNotification notification) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('$type 알림: ${notification.title}'),
          content: Text('${notification.body}'),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text('확인'),
            ),
          ],
        );
      },
    );
  }
...

 

FirebaseMessaging.onMessage.listen : 앱이 Foreground상태일때 메시지 수신처리
 
FirebaseMessaging.onMessageOpenedApp.listen : 앱이 Background상태일때 메시지 수신처리
 
RemoteMessage? message = await fcm.getInitialMessage(); : 앱이 Terminated(종료)상태일때, 사용자가 알림을 클릭하면서 앱이 실행될때 메시지 수신처리