1. Package 설치

flutter pub add firebase_messaging
flutter pub outdated 

 

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(종료)상태일때, 사용자가 알림을 클릭하면서 앱이 실행될때 메시지 수신처리